借助 Unsloth GRPO 微调训练构建个人专属 DeepSeek R1 推理模型

在这里插入图片描述

名称 配置 链接
R1基础模型 DeepSeek-R1-Distill-Qwen-1.5B https://modelscope.cn/models/deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B
数据集 nullskymc/ruozhiba_R1 https://modelscope.cn/datasets/nullskymc/ruozhiba_R1
医疗数据集 FreedomIntelligence/medical-o1-reasoning-SFT https://modelscope.cn/search?search=FreedomIntelligence%2Fmedical-o1-reasoning-SFT
名称 配置/版本 备注
操作系统 Ubuntu 22.04.2 LTS linux子系统需要win10/1的专业版
显卡 RTX 4060
内存 32G
GPU 8G
python 3.11
conda 25.1.1
cuda 12.4
CuDNN 8.9.7
镜像源 魔塔社区
一、系统安装
1.1. 查看wsl 可安装列表
wsl --list --online

在这里插入图片描述

1.2. 安装Ubuntu 22.04.2
wsl --install -d Ubuntu-22.04

在这里插入图片描述

设置用户名和密码zhz/123456
在这里插入图片描述

1.3. 更新系统依赖
sudo apt update && sudo apt upgrade -y
1.4. 安装conda

以下方式任选其一:

  • 方式1:单行执行
mkdir -p ~/miniconda3

wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh

bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3

rm -rf ~/miniconda3/miniconda.sh

~/miniconda3/bin/conda init bash
~/miniconda3/bin/conda init zsh
suorce ~/.bashrc
  • 方式2:整合执行
mkdir -p ~/miniconda3 && wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh && bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3 && rm -rf ~/miniconda3/miniconda.sh && ~/miniconda3/bin/conda init bash && suorce ~/.bashrc
suorce ~/.bashrc
conda -V

conda 25.1.1

conda create -n unsloth jupyterlab python=3.11 -y&conda activate unsloth
conda install -c conda-forge jupyterlab-lsp python-lsp-server
pip install jupyterlab-language-pack-zh-CN

pip install torch==2.4.1 torchvision==0.19.1 torchaudio==2.4.1 --index-url https://download.pytorch.org/whl/cu124

cd unsloth
pip install unsloth vllm && pip install --upgrade pillow
conda activate unsloth
conda activate unsloth
cd unsloth
nvidia-smi

在这里插入图片描述

1.5. 安装cuda
wget https://developer.download.nvidia.com/compute/cuda/12.4.0/local_installers/cuda_12.4.0_550.54.14_linux.run

CuDNN下载地址:

https://developer.nvidia.com/downloads/compute/cudnn/secure/8.9.7/local_installers/12.x/cudnn-linux-x86_64-8.9.7.29_cuda12-archive.tar.xz

复制到/home/zhz目录下
在这里插入图片描述

sudo tar -xvf cudnn-linux-x86_64-8.9.7.29_cuda12-archive.tar.xz
sudo sh cuda_12.4.0_550.54.14_linux.run
sudo apt update
sudo apt install gcc-11 g++-11
sudo sh cuda_12.4.0_550.54.14_linux.run

在这里插入图片描述
默认在这里插入图片描述
修改后

在这里插入图片描述

加入环境变量

nano ~/.bashrc
export PATH=/usr/local/cuda-12.4/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-12.4/lib64:$LD_LIBRARY_PATH
source ~/.bashrc
nvcc -V

在这里插入图片描述
在这里插入图片描述

sudo cp /home/zhz/cudnn-linux-x86_64-8.9.7.29_cuda12-archive/lib/* /usr/local/cuda-12.4/lib64/

cuda12.4版本安装

ls /usr/local/cuda-12.4/lib64/libcudnn*   # 验证是否CP成功

在这里插入图片描述

sudo cp /home/zhz/cudnn-linux-x86_64-8.9.7.29_cuda12-archive/include/* /usr/local/cuda-12.4/include
cat /usr/local/cuda-12.4/include/cudnn_version.h | grep CUDNN_MAJOR -A 2

在这里插入图片描述

sudo rm -rf cudnnlinux-x86_64-8.9.7.29_cudal2-archive.tar.xz
sudo rm -rf cudnn iinux-x86 64-8.9.7.29 cudal2-archive
sudo rm -rf cuda_12.4.0_550.54.14_linux.run cudnn-linux-x86_64-8.9.7.29_cudal2-archive.tar.xz:Zone.Identifier
conda activate unsloth
cd unsloth
conda install ipykernel -y
python -m ipykernel install --user --name=unsloth
pip install vllm
pip install --upgrade pillow
jupyter lab

接下来就是训练了。
模型下载

git lfs install
git clone https://www.modelscope.cn/deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B.git

千问模型(暂时没有用到,后续会用到)

git lfs install
git clone https://www.modelscope.cn/Qwen/Qwen2.5-3B-Instruct.git

数据集下载

git lfs install
git clone https://www.modelscope.cn/datasets/nullskymc/ruozhiba_R1.git

医疗数据集(暂时没有用到,后续会用到)

git lfs install
git clone https://www.modelscope.cn/datasets/FreedomIntelligence/medical-o1-reasoning-SFT.git
二、jupyter lab 服务启动
2.1. 激活 unsloth 环境
conda activate unsloth
2.2. 启动jupyter lab 服务
cd unsloth
jupyter lab

在这里插入图片描述

2.3. 进行jupyter lab 控台

访问浏览器,根据自己的实际情况

http://localhost:8888/lab?token=43c0043ce4726eac3928f3056d969f6cc4809d1fb2ac79d3

在这里插入图片描述

三、DeepSeek R1 微调训练
3.1. Unsloth 安装模型配置

在这里插入图片描述

# 步骤一:使用深度学习框架和相关库来加载、配置和优化一个预训练语言模型,并为其添加低秩近似(LoRA)适配层,以实现高效的文本生成和推理任务
# 总结:
# 这段代码的核心目的在于:加载和配置预训练模型:使用 4bit 量化加载大型语言模型(如 Qwen 2.5-3B),减少显存占用。
# 启用 vLLM 的高性能推理模式,加速文本生成。添加 LoRA 适配层:通过 LoRA 方法微调模型,使其能够在特定任务上表现更好。
# 控制适配层的秩和缩放因子,平衡模型性能和资源占用。优化推理过程:启用梯度检查点机制,减少显存占用。
# 控制 GPU 内存使用比例,确保模型能够在硬件资源限制下稳定运行。最终,这段代码实现了一个高效、可扩展且资源优化的大语言模型部署方案。

# 这段代码的主要目的是通过 unsloth 库中的 PatchFastRL 函数,将 GRPO 补丁应用到 FastLanguageModel 类,以实现对语言模型的强化学习优化。
# 这通常用于提高模型在特定任务上的性能,例如对话生成、文本摘要等。
from unsloth import FastLanguageModel, PatchFastRL
PatchFastRL("GRPO", FastLanguageModel)

from unsloth import is_bfloat16_supported
import torch
max_seq_length = 512 # 输入序列的最大长度,控制模型处理的输入和输出文本的最大长度。较大的值允许模型处理更长的文本,但可能会增加内存占用和推理时间。
lora_rank = 32 # 定义了 LoRA 适配层的秩。较大的秩可以提升模型的表达能力,但也相应增加内存和计算开销。

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "/home/zhz/DeepSeek-R1-Distill-Qwen-1.5B",
    max_seq_length = max_seq_length,
    load_in_4bit = True, # 使用 4bit 量化加载模型,节省显存
    fast_inference = True, # 启用 vLLM 的高性能推理模式
    max_lora_rank = lora_rank,  
    gpu_memory_utilization = 0.6, # 控制 GPU 内存的使用比例
)

model = FastLanguageModel.get_peft_model(
    model,
    r = lora_rank, # Choose any number > 0 ! Suggested 8, 16, 32, 64, 128 ;LoRA 的秩(与之前设置的 lora_rank 一致)
    target_modules = [
        "q_proj", "k_proj", "v_proj", "o_proj",
        "gate_proj", "up_proj", "down_proj",
    ], # 指定需要应用 LoRA 的模型模块
    lora_alpha = lora_rank,  # LoRA 的缩放因子(通常与秩相关)
    use_gradient_checkpointing = "unsloth", # 启用梯度检查点机制(减少显存占用)
    random_state = 3407,   # 设置随机种子,确保可复现性
)

在这里插入图片描述

3.2. 数据集配置和加载

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

import re
from datasets import load_dataset, Dataset

# Load and prep dataset
SYSTEM_PROMPT = """
Respond in the following format:
<reasoning>
...
</reasoning>
<answer>
...
</answer>
"""

XML_COT_FORMAT = """\
<reasoning>
{
    
    reasoning}
</reasoning>
<answer>
{
    
    answer}
</answer>
"""
# 从 XML 格式的文本中提取 <answer> 标签内的内容
def extract_xml_answer(text: str) -> str:
    """从 XML 格式的文本中提取 <answer> 标签内的内容。"""
    answer = text.split("<answer>")[-1]
    answer = answer.split("</answer>")[0]
    return answer.strip()
# 从包含 <think> 标签的文本中提取实际回答内容
def extract_think_answer(text: str) -> str:
    """从包含 <think> 标签的文本中提取实际回答内容。"""
    return text.split("</think>")[-1].strip()

# 
def get_a_questions(split="train", local_path="/home/zhz/datasets/ruozhiba_R1/alpaca_output.jsonl") -> Dataset:
    """
    加载本地 alpaca_output.jsonl 数据集。
    
    Args:
        split (str): 数据集划分,默认为 "train"。
        local_path (str): 本地数据集路径,默认为 "/p/to/your/alpaca_output.jsonl"。
    ath
    Returns:
        Dataset: 处理后的数据集。
    """
    # 从本地路径加载数据集
    data = load_dataset('json', data_files=local_path, split=split)  # type: ignore
    
    # 根据 Alpaca 格式进行字段映射
    data = data.map(lambda x: {
    
      # type: ignore
        'prompt': [
            {
    
    'role': 'system', 'content': SYSTEM_PROMPT},
            {
    
    'role': 'user', 'content': x['instruction']}
        ],
        'answer': extract_think_answer(x['output'])  # 提取 <think> 标签后的回答
    })  # type: ignore
    
    return data 

dataset = get_a_questions()

# 奖励函数:正确性奖励
def correctness_reward_func(prompts, completions, answer, **kwargs) -> list[float]:
    responses = [completion[0]['content'] for completion in completions]
    q = prompts[0][-1]['content']
    extracted_responses = [extract_xml_answer(r) for r in responses]
    print('-'*20, f"Question:\n{q}", f"\nAnswer:\n{answer[0]}", f"\nResponse:\n{responses[0]}", f"\nExtracted:\n{extracted_responses[0]}")
    return [2.0 if r == a else 0.0 for r, a in zip(extracted_responses, answer)]
# 奖励函数:整数奖励
def int_reward_func(completions, **kwargs) -> list[float]:
    responses = [completion[0]['content'] for completion in completions]
    extracted_responses = [extract_xml_answer(r) for r in responses]
    return [0.5 if r.isdigit() else 0.0 for r in extracted_responses]
# 奖励函数:严格格式奖励
def strict_format_reward_func(completions, **kwargs) -> list[float]:
    """Reward function that checks if the completion has a specific format."""
    pattern = r"^<reasoning>\n.*?\n</reasoning>\n<answer>\n.*?\n</answer>\n$"
    responses = [completion[0]["content"] for completion in completions]
    matches = [re.match(pattern, r) for r in responses]
    return [0.5 if match else 0.0 for match in matches]
# 奖励函数:宽松格式奖励
def soft_format_reward_func(completions, **kwargs) -> list[float]:
    """Reward function that checks if the completion has a specific format."""
    pattern = r"<reasoning>.*?</reasoning>\s*<answer>.*?</answer>"
    responses = [completion[0]["content"] for completion in completions]
    matches = [re.match(pattern, r) for r in responses]
    return [0.5 if match else 0.0 for match in matches]
# 计算 XML 标签数量
def count_xml(text) -> float:
    count = 0.0
    if text.count("<reasoning>\n") == 1:
        count += 0.125
    if text.count("\n</reasoning>\n") == 1:
        count += 0.125
    if text.count("\n<answer>\n") == 1:
        count += 0.125
        count -= len(text.split("\n</answer>\n")[-1])*0.001
    if text.count("\n</answer>") == 1:
        count += 0.125
        count -= (len(text.split("\n</answer>")[-1]) - 1)*0.001
    return count
# 奖励函数:XML 标签计数奖励
def xmlcount_reward_func(completions, **kwargs) -> list[float]:
    contents = [completion[0]["content"] for completion in completions]
    return [count_xml(c) for c in contents]

在这里插入图片描述

3.3. 配置 GRPO 训练参数

在这里插入图片描述

from trl import GRPOConfig, GRPOTrainer

# 配置 GRPO 训练参数
training_args = GRPOConfig(
    use_vllm=True,  # 使用 vLLM 进行快速推理
    learning_rate=5e-6,  # 学习率
    adam_beta1=0.9,  # Adam 优化器的 beta1 参数
    adam_beta2=0.99,  # Adam 优化器的 beta2 参数
    weight_decay=0.1,  # 权重衰减
    warmup_ratio=0.1,  # 学习率预热比例
    lr_scheduler_type="cosine",  # 学习率调度器类型
    optim="paged_adamw_8bit",  # 优化器类型
    logging_steps=1,  # 日志记录步数
    bf16=is_bfloat16_supported(),  # 是否支持 bfloat16
    fp16=not is_bfloat16_supported(),  # 是否支持 fp16
    per_device_train_batch_size=1,  # 每个设备的训练批量大小,根据 GPU 内存调整
    gradient_accumulation_steps=1,  # 梯度累积步数,根据 GPU 内存调整
    num_generations=6,  # 生成数量,根据 GPU 内存调整
    max_prompt_length=256,  # 最大提示长度
    max_completion_length=200,  # 最大完成长度
    max_steps=300,  # 最大训练步数
    save_steps=300,  # 保存步数
    max_grad_norm=0.1,  # 最大梯度范数
    report_to="none",  # 日志报告目标,可以使用 Weights & Biases
    output_dir="outputs",  # 输出目录
)
3.4. GRPOTrainer配置和加载

在这里插入图片描述
在这里插入图片描述

trainer = GRPOTrainer(
    model=model,  # 模型
    processing_class=tokenizer,  # 分词器
    reward_funcs=[  # 奖励函数列表
        xmlcount_reward_func, # XML结构奖励
        soft_format_reward_func, # 宽松格式奖励
        strict_format_reward_func, # 严格格式奖励
        int_reward_func, # 整数奖励
        correctness_reward_func, # 正确性奖励
    ],
    args=training_args,  # 训练参数
    train_dataset=dataset,  # 训练数据集
)
trainer.train()  # 开始训练

在这里插入图片描述

3.5. 文本生成任务实现流程

在这里插入图片描述

# 用户请求: 用户提供一段文字作为输入
text = tokenizer.apply_chat_template([
    {
    
    "role": "user", "content": "你是谁,开始你的表演"},  # 用户输入
], tokenize=False, add_generation_prompt=True)  # 将用户请求封装成对话模板

from vllm import SamplingParams

# 定义采样参数
sampling_params = SamplingParams(
    temperature=0.8,  # 控制输出的多样性
    top_p=0.95,  # 核采样参数
    max_tokens=1024,  # 生成文本的最大长度
)

# 调用模型生成文本
output = model.fast_generate(
    [text],  # 输入文本内容
    sampling_params=sampling_params,  # 采样参数
    lora_request=None,  # LoRA 请求
)[0].outputs[0].text  # 获取生成结果中的第一个样本的文本内容

output  # 输出生成的文本
3.6. 保存 LoRA 权重

在这里插入图片描述

model.save_lora("grpo_saved_lora")
3.7. 加载 LoRA 权重并进行推理

在这里插入图片描述

# 加载之前保存的 LoRA 权重(通过 "grpo_saved_lora")
# 使用这些权重和配置的参数生成回答
# 返回生成的文本结果
text = tokenizer.apply_chat_template([
    {
    
    "role": "system", "content": SYSTEM_PROMPT},  # 系统提示
    {
    
    "role": "user", "content": "你是谁,开始你的表演"},  # 用户输入
], tokenize=False, add_generation_prompt=True)  # 将用户请求封装成对话模板

from vllm import SamplingParams

# 定义采样参数
sampling_params = SamplingParams(
    temperature=0.8,  # 控制输出的多样性
    top_p=0.95,  # 核采样参数
    max_tokens=1024,  # 生成文本的最大长度
)
# 调用模型生成文本
output = model.fast_generate(
    text,  # 输入文本内容
    sampling_params=sampling_params,  # 采样参数
    lora_request=model.load_lora("grpo_saved_lora"),  # 加载 LoRA 权重
)[0].outputs[0].text  # 获取生成结果中的第一个样本的文本内容

output  # 输出生成的文本
3.8. 合并为 16bit 格式

在这里插入图片描述

model.save_pretrained_merged("model", tokenizer, save_method="merged_16bit")

猜你喜欢

转载自blog.csdn.net/weixin_40816738/article/details/145817123
今日推荐