1. 背景

目前在中文领域,DeepSeek 和 Qwen 可以算是第一梯队的模型了。本次内容,我们就以 Qwen-1.8B 这个小模型为例,一步步演示下如何进行 PEFT 微调。

Qwen-1.8B 是 Qwen 系列的一个最小的版本,具有 18 亿参数。这个模型虽然在大模型家族中量级较小,但也能够处理中英双语的自然语言理解和生成任务。通过量化技术,Qwen-1.8B 可以在低至 4GB 显存的消费级显卡上运行(INT4 量化级别)。

Qwen-1.8B 在性能和开放性之间进行了良好的平衡,旨在为开发者提供一个强大且易用的对话交互基座,加速大模型在实际场景中的应用落地,很受中文世界用户的欢迎。这个模型的大小,也比较适合我们学习。

这是一个开源模型,可以直接从 HuggingFace 上下载。

2. 微调技术概览

我们之所以要对大模型继续微调,通常是要实现两个目标:第一个是让模型更切合自己的应用场景,更加适应特定的下游任务;第二个是模型能够变得更加轻便,节省资源。

针对这两个目标,业界主要采用以下几种微调方式:

不同的微调方法在参数效率、任务适应能力、实现难易度等方面各有优劣。针对具体任务,需要根据实际情况(如任务复杂度、训练数据规模、可用算力等)来选择合适的微调策略。

此外,基于模型部署的需求,模型蒸馏、量化、剪枝等模型压缩方法,也常常与微调方法配合使用,以进一步减小模型体积、加速推理速度,使大模型更容易在实际应用中落地,这个我们在后面的内容中会展开讨论。

3. LoRA 简介

在上面罗列的各种主流微调方法,除了全量微调之外,其余都可以称之为 PEFT,即 Parameter Efficient Fine-Tuning,参数高效微调,它是一种在大语言模型微调过程中,通过减少需要更新的参数数量来降低计算成本和存储需求的技术。

毕竟大语言模型的主要问题是参数数量过大,做全量微调实在对资源的消耗太大,已经不是一般研究人员或者普通企业所能做的了。而 PEFT 则是当前大模型领域最为高效实用的微调技术之一!

PEFT 微调中的一条重要分支,就是基于低秩分解的方法(LoRA),这一类方法通过低秩分解来近似表示要学习的增量权重矩阵。

基于低秩分解的思想,可以用非常小的参数开销(新增参数量通常只有原模型的 0.1%~3%)来适配模型,在参数效率上非常有优势。

关于 LoRA 的更多技术细节,可以参考论文《LoRA: Low-Rank Adaptation of Large Language Models》。

LoRA 这种方法有许多优点:

  • LoRA 通过大幅减少可训练参数的数量,使微调更加高效。
  • 原始预训练权重保持冻结,这意味着你可以拥有多个轻量级和便携式的LoRA 模型,以便在其基础上构建各种下游任务。
  • LoRA 与其他参数高效方法正交,并且可以与其中许多方法相结合。
  • 使用 LoRA 微调的模型的性能,与完全微调的模型的性能相当。

凭借参数少、计算快、效果好、易实现等优势,LoRA 成为了近年来最受欢迎的 PEFT 技术之一。包括 ChatGPT 在内的众多业界大模型,都采用了 LoRA 进行下游任务适配。

所以,接下来我们就通过 LoRA 方法,来尝试微调 Qwen-1.8B 模型!

4. 环境准备

首先说明下我本次微调所使用的环境:

  • 操作系统:ubuntu-22.04
  • GPU:RTX 3090(24GB)
  • CUDA 环境:CUDA 12.6
  • Python 版本:Python 3.10
  • PyTorch 版本:PyTorch 2.7.0

需要特别注意的是:PyTorch 与 cuda 的版本一定要匹配,否则可能无法正常运行项目。

具体版本的对应关系可以参考 PyTorch 官网:

要从零开始手动实现上面的各种微调方法,当然不现实,好在我们有 Hugging Face 的 PEFT 框架。

PEFT 是 Hugging Face 针对大模型微调而开发的一套框架,它旨在使大语言模型(LLMs)的微调过程更加高效和资源友好。这个框架提供了一些主要的技术,以减少微调所需的参数量和计算资源,同时仍保持模型在特定任务上的高性能。

除了 peft 之外,我们微调还要依赖其它一些包,这里可以一起安装下:

pip install peft transformers torch setuptools bitsandbytes datasets sentencepiece

因为 peft 和 transformers 库都需要访问 HuggingFace,为了加速访问,我们可以通过环境变量设置下镜像站:

import os
# 设置模型下载路径
os.environ["HF_HOME"] = "你的下载路径"
os.environ["HF_HUB_CACHE"] = "你的下载路径"
# 设置HuggingFace镜像站
os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"

5. 准备数据

环境已经就绪,下面就开始我们的微调实战吧!

通常来说,微调的第一步就是准备数据。

我在使用 ChatGLM 提供的一个 Alpaca 风格的中文中药数据集,大概有4千条,可以用来作为我们本次微调的训练语料。

Alpaca 是由 Meta 发布的一种主流的数据集格式,每条语料都包含 instruction、input 和 output 这三个字段。 关于 Alpaca 风格的具体解释,这里就不赘述了,大家感兴趣的话可以自行搜索一下。

在微调之前,我们先测试一下 Qwen1.5-1.8B-Chat 这个模型,看看微调前的效果。我向它提问几条中药相关的问题:

# 导入Hugging Face Transformers相关库
from transformers import AutoModelForCausalLM, AutoTokenizer
# 指定模型名称
MODEL = "Qwen/Qwen1.5-1.8B-Chat"
# 加载训练好的模型和分词器
# tokenizer和model要一一对应
tokenizer = AutoTokenizer.from_pretrained(MODEL, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(MODEL, trust_remote_code=True, device_map='auto')
# 模型设为评估状态
model.eval()
# 定义测试示例
examples = [
{
"instruction": "使用中医知识正确回答适合这个病例的中成药。",
"input": "我这段时间感觉身体不太对劲,有腹泻的迹象,面黄肌瘦,吃点什么中成药能改善?"
},
{
"instruction": "使用中医知识正确回答适合这个病例的中成药。",
"input": "我昨天开始咳嗽,感觉喉咙痛,痰又稠又黄,还感觉有点发热。"
}
]
# 测试模型生成结果
for example in examples:
context = f"Instruction: {example['instruction']}\nInput: {example['input']}\nAnswer: "
# 对输入文本进行编码
inputs = tokenizer(context, return_tensors="pt")
# 模型生成回复
outputs = model.generate(inputs.input_ids.to(model.device), max_length=512, num_return_sequences=1, no_repeat_ngram_size=2)
# 对回复内容进行解码
answer = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(f"Input: {example['input']}")
print(f"Output: {answer}\n")

执行这段代码,会首先从 HuggingFace 上,将模型下载到本地,之后采用本地的模型进行推理。

需要注意的是:因为我们还没有进行微调,所以此时模型生成的结果还是基于之前预训练的语料库,并不一定是真实准确的。

6. 执行微调

我们确认了模型已经被拉取到本地,并且可以正常运行,接下来,就可以正式开始微调了。

首先,我们需要自定义数据集,继承 Pytorch 的 Dataset 类,这样就将刚才下载下来的数据集转化成模型可以理解的格式:

# 导入Dataset
from torch.utils.data import Dataset
import json
# 自定义中药数据集
class MedicineDataset(Dataset):
def __init__(self, data_path, tokenizer, device):
self.data = json.load(open(data_path))
self.tokenizer = tokenizer
self.device = device
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
example = self.data[idx]
formatted_example = self.format_example(example)
inputs = self.tokenizer(
formatted_example["context"],
max_length=512,
truncation=True,
padding="max_length",
return_tensors="pt"
)
labels = self.tokenizer(
formatted_example["target"],
max_length=512,
truncation=True,
padding="max_length",
return_tensors="pt"
)
inputs["labels"] = labels["input_ids"]
# 确保所有张量在同一个设备上
return {key: val.squeeze().to(self.device) for key, val in inputs.items()}
def format_example(self, example: dict) -> dict:
context = f"Instruction: {example['instruction']}\n"
if example.get("input"):
context += f"Input: {example['input']}\n"
context += "Answer: "
target = example["output"]
return {"context": context, "target": target}

接下来,对需要微调的模型进行一些设置:

# 导入微调模型所需的库
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch
# 指定模型
MODEL = "Qwen/Qwen1.5-1.8B-Chat"
# 判断设备类型
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 加载分词器和模型
tokenizer = AutoTokenizer.from_pretrained(MODEL, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(MODEL, trust_remote_code=True)
# 把模型移动到设备上
model = model.to(device)
# 节省内存的一些配置
model.supports_gradient_checkpointing = True  # 支持梯度检查点功能,减少显存使用
model.gradient_checkpointing_enable()  # 启用梯度检查点功能,减少训练时的显存占用
model.enable_input_require_grads()  # 允许模型输入的张量需要梯度,支持更灵活的梯度计算
model.is_parallelizable = True  # 指定模型可以并行化处理
model.model_parallel = True  # 启用模型并行化,在多设备(如多GPU)上分布计算

然后是比较关键的一步:LoRA 配置。

这些配置项可以帮助我们在训练语言模型时应用 LoRA 技术,通过低秩分解,减少模型参数和内存占用,并使用 dropout 技术防止过拟合,同时仅在特定模块上应用 LoRA,从而在计算效率和模型性能之间取得平衡。

# 导入peft库
from peft import get_peft_model, LoraConfig, TaskType
# 配置LoRA相关参数
peft_config = LoraConfig(
task_type=TaskType.CAUSAL_LM, # 任务类型: 因果语言模型(Causal LM)
inference_mode=False, # 设置推理模式为False,表示当前配置用于训练模式,而非推理模式
r=8, # 设置低秩分解的秩: Rank=8。r越小,表示模型的参数量和内存占用越少,压缩程度越高
lora_alpha=32, # 设置缩放因子:lora_alpha=32
lora_dropout=0.1, # 设置dropout概率:lora_dropout=0.1,表示有10%的神经元会被丢弃,此操作有助于防止模型过拟合,提升模型的泛化能力
target_modules=["q_proj", "v_proj"]  # 查询投影和值投影模块
)
# 在模型上应用LoRA配置
model = get_peft_model(model, peft_config)

相关的前置工作已经都 ready 了,下一步就可以直接进行微调了:

# 创建训练数据集
train_dataset = MedicineDataset("./data/dataset/cpmi_dataset.json", tokenizer, device)
# 导入训练相关的库
from transformers import TrainingArguments, Trainer
# 定义训练参数
training_args = TrainingArguments(
output_dir="./results",  # 训练结果保存的目录
num_train_epochs=50,  # 训练的总轮数
per_device_train_batch_size=4,  # 每个设备上的训练批次大小
gradient_accumulation_steps=8,  # 梯度累积步数,在进行反向传播前累积多少步
# evaluation_strategy="no",  # 评估策略,这里设置为不评估
save_strategy="epoch",  # 保存策略,每个 epoch 保存一次模型
learning_rate=5e-5,  # 学习率
fp16=True,  # 启用 16 位浮点数训练,提高训练速度并减少显存使用
logging_dir="./logs",  # 日志保存目录
dataloader_pin_memory=False,  # 禁用pin_memory以节省内存
)
# 自定义 Trainer
class CustomTrainer(Trainer):
def compute_loss(self, model, inputs, return_outputs=False, **kwargs):
labels = inputs.pop("labels")  # 从输入中取出标签
outputs = model(**inputs)  # 获取模型输出
logits = outputs.logits  # 获取模型输出的logits
shift_logits = logits[..., :-1, :].contiguous()  # 对logits进行偏移,准备计算交叉熵损失
shift_labels = labels[..., 1:].contiguous()  # 对标签进行偏移,准备计算交叉熵损失
loss_fct = torch.nn.CrossEntropyLoss()  # 定义交叉熵损失函数
loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))  # 计算损失
return (loss, outputs) if return_outputs else loss  # 根据参数返回损失和输出
# 定义 Trainer
trainer = CustomTrainer(
model=model,  # 训练的模型
args=training_args,  # 训练参数
train_dataset=train_dataset,  # 训练数据集
)
# 开始微调
print("开始微调")
trainer.train()
print("微调完成")

执行这段代码,就可以把模型训练 run 起来了。

我们指定了训练的轮数为50轮,在实际的生产环境中,训练的轮数通常会更多,并且要结合不断的调参,才能达到较好的训练效果。训练的耗时可能会比较长,请大家耐心等待~

这里我们可以看到,随着模型训练的轮数越来越多,损失函数也在逐渐收敛。

跑完训练之后,我们可以将微调好的模型保存到本地。

import os
# 微调后模型的保存路径
FINE_TUNING_DIR = "./fine-tuning-qwen1.5-1.8b-chat"
# 保存训练后的模型和配置文件
model.save_pretrained(FINE_TUNING_DIR)
tokenizer.save_pretrained(FINE_TUNING_DIR)
# 将配置文件下载到模型目录中
config = model.config.to_dict()
config_path = os.path.join(FINE_TUNING_DIR, 'config.json')
with open(config_path, 'w') as f:
json.dump(config, f, ensure_ascii=False, indent=4)
print(f"微调后的模型已经成功保存到: {FINE_TUNING_DIR}")

这样一来,整个微调过程就结束了,是不是没有那么复杂?

7. 模型评估

微调完成之后,最后一步就是模型评估,也就是在微调后的模型上再次测试同样的问题,看看模型生成的回复是否与参考数据集中的内容一致。

# 导入所需要的库
from transformers import AutoModelForCausalLM, AutoTokenizer
# 加载训练好的模型和分词器
tokenizer = AutoTokenizer.from_pretrained(FINE_TUNING_DIR, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(FINE_TUNING_DIR, trust_remote_code=True, device_map='auto')
# 模型设为评估状态
model.eval()
# 定义测试示例
questions = [
{
"instruction": "使用中医知识正确回答适合这个病例的中成药。",
"input": "我这段时间感觉身体不太对劲,有腹泻的迹象,面黄肌瘦,吃点什么中成药能改善?"
},
{
"instruction": "使用中医知识正确回答适合这个病例的中成药。",
"input": "我昨天开始咳嗽,感觉喉咙痛,痰又稠又黄,还感觉有点发热。"
}
]
# 生成回复
for question in questions:
context = f"Instruction: {question['instruction']}\nInput: {question['input']}\nAnswer: "
# 对输入文本进行编码
inputs = tokenizer(context, return_tensors="pt")
# 模型生成回复
outputs = model.generate(inputs.input_ids.to(model.device), max_length=512, num_return_sequences=1, no_repeat_ngram_size=2)
# 对回复内容进行解码
answer = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(f"Input: {question['input']}")
print(f"Output: {answer}\n")

实际执行结果如下:

令人欣喜的是,我们训练语料库中的知识,已经被 Qwen 模型消化进去了,并且体现到了生成的内容中,说明本次微调是生效的!

同样可以看到,目前回答的准确率还不是很高,语义也不太通顺,这是因为我们训练的轮数太少,而且也没有针对性地设置 LoRA 参数,这是一个需要反复调优和评估的过程,这里就不展开讨论了,鼓励大家自己动手试一试。

8. 小结

本篇文章,我们介绍了微调的核心概念,并基于 LoRA 技术与 peft 框架,对 Qwen 模型进行了微调,使其在中医领域的问答任务上取得了很好的效果。

利用 LoRA 等参数高效微调技术,我们可以用较小的计算开销,快速适配预训练模型到特定任务,让模型获得新的领域知识,同时又能保留原有的语言理解和生成能力,这为大语言模型在垂直领域的应用提供了新的思路,也降低了大模型应用落地的门槛。

如何学习大模型 AI ?

由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。

但是具体到个人,只能说是:

“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。

这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。

我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

在这里插入图片描述

第一阶段(10天):初阶应用

该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。

  • 大模型 AI 能干什么?
  • 大模型是怎样获得「智能」的?
  • 用好 AI 的核心心法
  • 大模型应用业务架构
  • 大模型应用技术架构
  • 代码示例:向 GPT-3.5 灌入新知识
  • 提示工程的意义和核心思想
  • Prompt 典型构成
  • 指令调优方法论
  • 思维链和思维树
  • Prompt 攻击和防范

第二阶段(30天):高阶应用

该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。

  • 为什么要做 RAG
  • 搭建一个简单的 ChatPDF
  • 检索的基础概念
  • 什么是向量表示(Embeddings)
  • 向量数据库与向量检索
  • 基于向量检索的 RAG
  • 搭建 RAG 系统的扩展知识
  • 混合检索与 RAG-Fusion 简介
  • 向量模型本地部署

第三阶段(30天):模型训练

恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。

到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?

  • 为什么要做 RAG
  • 什么是模型
  • 什么是模型训练
  • 求解器 & 损失函数简介
  • 小实验2:手写一个简单的神经网络并训练它
  • 什么是训练/预训练/微调/轻量化微调
  • Transformer结构简介
  • 轻量化微调
  • 实验数据集的构建

第四阶段(20天):商业闭环

对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。

  • 硬件选型
  • 带你了解全球大模型
  • 使用国产大模型服务
  • 搭建 OpenAI 代理
  • 热身:基于阿里云 PAI 部署 Stable Diffusion
  • 在本地计算机运行大模型
  • 大模型的私有化部署
  • 基于 vLLM 部署大模型
  • 案例:如何优雅地在阿里云私有部署开源大模型
  • 部署一套开源 LLM 项目
  • 内容安全
  • 互联网信息服务算法备案

学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。

如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

在这里插入图片描述

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐