【大模型】大模型(Qwen2.5)_GRPO训练教程_AutoDL

大模型(Qwen2.5_Coder_3B) GRPO训练教程

本教程主要目的是完成 在 AutoDL 的GPU服务器 4090 上,对 大模型(Qwen2.5_Coder_3B)进行GRPO训练,所使用的数据集是 “gms8k”。

当然,也可以使用其他类型的GPU,实现训练过程中 24G的显存也会用到了7G, 模型可以完美切换到其他Qwen模型进行微调,我这里只是为了演示选择了较少的3B模型。

教程内容

本教程将介绍以下内容:

  1. AutoDL配置 - 如何启动相应配置的GPU实例
  2. 安装依赖库 - 如何安装python依赖包
  3. 模型准备 - 如何下载和初始化模型
  4. 数据准备 - 如何准备和处理训练数据
  5. 模型训练 - 如何训练和优化模型
  6. 模型保存 - 如何保存训练结果
  7. 模型推理 - 如何使用训练好的模型进行推理

【本教程代码 】: https://github.com/songxia928/LLM/blob/main/02_%E5%A4%A7%E6%A8%A1%E5%9E%8BQwen2.5_GRPO%E8%AE%AD%E7%BB%83.ipynb


0.AutoDL配置

  • 为什么选择 AutoDL?: 相对于其他云服务器厂商,AutoDL卡相对便宜很多,而且操作相对简单,上手成本很低。
  • 如何配置?
    • GPU: RTX 4090(24GB) * 1。
    • 镜像: PyTorch 2.3.0 --> Python 3.12(ubuntu22.04) --> CUDA 12.1

在这里插入图片描述


1.安装依赖库

!pip install unsloth vllm modelscope datasets
!pip install --upgrade packaging  # 新增依赖升级
!pip install peft

2.模型准备

下面代码是直接从模型文件中读取,模型文件下载的部分可以参考上一篇文章 “大模型(Qwen2.5_Coder_3B) 指令微调教程” 的 “2.1下载模型”。

2.1 模型加载

from unsloth import FastLanguageModel


PatchFastRL("GRPO", FastLanguageModel)


# 基础配置参数
max_seq_length = 2048 # 最大序列长度
dtype = None # 自动检测数据类型
load_in_4bit = True # 使用4位量化以减少内存使用
lora_rank = 64   # 选择任何大于 0 的数, 建议 8, 16, 32, 64, 128



# 加载预训练模型和分词器
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = local_dir, # "unsloth/Qwen2.5-Coder-32B-Instruct", # 选择Qwen2.5 3B指令模型
    max_seq_length = max_seq_length,
    dtype = dtype,
    load_in_4bit = load_in_4bit,
)

model = FastLanguageModel.get_peft_model(
    model,
    r = lora_rank, # LoRA秩,控制可训练参数数量
    target_modules = ["q_proj", "k_proj", "v_proj", "o_proj",
                      "gate_proj", "up_proj", "down_proj",], # 需要训练的目标模块
    lora_alpha = lora_rank, # LoRA缩放因子
    lora_dropout = 0, # LoRA dropout率
    bias = "none", # 是否训练偏置项
    use_gradient_checkpointing = "unsloth", # 使用梯度检查点节省显存
    random_state = 3407, # 随机数种子
    use_rslora = False, # 是否使用稳定版LoRA
    loftq_config = None, # LoftQ配置
)

在这里插入图片描述

hugging face 也有对应的模型,但是AutoDL不能直接访问 hugging face,可以在本地PC下载,再上传到AutoDL,如下面的方式。但是这种方式比较复杂,不推荐。

在这里插入图片描述

2.2 未经过 GRPO 训练的模型推理

import torch
from transformers import GenerationConfig

# 应用聊天模板
text = tokenizer.apply_chat_template([
    {"role": "user", "content": "How many r's are in strawberry?"}
], tokenize=False, add_generation_prompt=True)

# 配置生成参数
generation_config = GenerationConfig(
    temperature=0.8,
    top_p=0.95,
    max_new_tokens=1024,
)

# 将文本转换为输入张量
input_ids = tokenizer(text, return_tensors="pt").input_ids.to(model.device)

# 使用标准的 generate 方法生成输出
with torch.no_grad():
    output = model.generate(
        input_ids=input_ids,
        generation_config=generation_config
    )

# 解码输出
output_text = tokenizer.decode(output[0], skip_special_tokens=True)

print("未经过监督微调的模型输出: ", output_text)

在这里插入图片描述


3. 数据准备

3.1 本地PC准备步骤

datasets 是 Hugging Face 提供的用于加载和处理各种数据集的库。AutoDL上无法直接访问 Hugging Face。 因此数据集 “mlabonne/FineTome-100k” 需要在本地PC下载后,从AutoDL的“文件存储”上传到你所使用的实例存储位置。

本地可以科学上网后,安装 pip install datasets 后,然后运行下面代码。

from datasets import load_dataset
import json

# 下载 gsm8k 数据集的训练集
dataset = load_dataset('openai/gsm8k', 'main', split='train')

# 保存为 JSON 文件
with open('./datasets/gsm8k/gsm8k_train.json', 'w', encoding='utf-8') as f:
    for example in dataset:
        json.dump(example, f, ensure_ascii=False)
        f.write('\n')

如果不能科学上网,无法下载对应数据集,我这里也提供了依据下载好的。链接: https://pan.baidu.com/s/1ftrbEn7FHZaYG3CjXEkqzA?pwd=sakn 提取码: sakn 。

随后需要将下载的数据上传到AutoDL 对应的位子 “./datasets/gsm8k”。

3.2 数据加载

# 本地数据集路径(请提前上传到AutoDL的 /auto-fs/datasets目录)
import re
import json
from datasets import Dataset


# 加载并准备数据集
SYSTEM_PROMPT = """
响应格式如下:
<reasoning>
...
</reasoning>
<answer>
...
</answer>
"""

XML_COT_FORMAT = """\
<reasoning>
{reasoning}
</reasoning>
<answer>
{answer}
</answer>
"""

def extract_xml_answer(text: str) -> str:
    """从文本中提取 XML 格式的答案"""
    answer = text.split("<answer>")[-1]
    answer = answer.split("</answer>")[0]
    return answer.strip()

def extract_hash_answer(text: str) -> str | None:
    """从文本中提取带有哈希的答案"""
    if "####" not in text:
        return None
    return text.split("####")[1].strip()

'''
def load_local_gsm8k(file_path="./datasets/gsm8k/gsm8k_train.json"):
    with open(file_path, "r", encoding="utf-8") as f:
        data = json.load(f)
    return Dataset.from_list(data)
'''

def load_local_gsm8k(file_path="./datasets/gsm8k/gsm8k_train.json"):
    data = []
    with open(file_path, "r", encoding="utf-8") as f:
        for line in f:
            data.append(json.loads(line.strip()))  # 逐行加载
    return Dataset.from_list(data)

# 加载本地数据集
dataset = load_local_gsm8k()

# 处理数据集
def process_dataset(data):
    data = data.map(lambda x: {
        'prompt': [
            {'role': 'system', 'content': SYSTEM_PROMPT},
            {'role': 'user', 'content': x['question']}
        ],
        'answer': extract_hash_answer(x['answer'])
    })
    return data

dataset = process_dataset(dataset)

# 奖励函数
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]:
    """严格格式奖励函数"""
    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]:
    """软格式奖励函数"""
    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]

def count_xml(text) -> float:
    """计算 XML 格式的得分"""
    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

def xmlcount_reward_func(completions, **kwargs) -> list[float]:
    """XML计数奖励函数"""
    contents = [completion[0]["content"] for completion in completions]
    return [count_xml(c) for c in contents]

在这里插入图片描述


4. 模型GRPO训练

4.1 数据模型验证

/models/
└── gsm8k/Qwen2.5-Coder-3B-Instruct   # 模型

/datasets/
└── gsm8k/
    └── gsm8k_train.json  # 数据集

/outputs/
└── 02_outputs/
    ├── grpo_saved_lora/     # LoRA适配器
    └── qwen-grpo-model/     # ModelScope格式模型
# 验证模型加载
from modelscope import Model
import pprint

loaded_model = Model.from_pretrained("/outputs/qwen-grpo-model")
assert loaded_model is not None, "模型加载失败"

# 验证数据集
assert len(dataset) > 0, "数据集加载失败"
pprint.pprint(dataset[0])  # 验证数据格式

4.2 训练

from trl import GRPOConfig, GRPOTrainer
from unsloth import is_bfloat16_supported

save_path = "./outputs/02_outputs"

training_args = GRPOConfig(
    use_vllm=False,  # 使用 vLLM 进行快速推理!
    learning_rate=5e-6,
    adam_beta1=0.9,
    adam_beta2=0.99,
    weight_decay=0.1,
    warmup_ratio=0.1,
    lr_scheduler_type="cosine",
    optim="adamw_8bit",
    logging_steps=1,
    bf16=is_bfloat16_supported(),  # 是否支持 bfloat16
    fp16=not is_bfloat16_supported(),  # 是否使用 fp16
    per_device_train_batch_size=1,
    gradient_accumulation_steps=1,  # 增加到 4 以获得更平滑的训练
    num_generations=8,  # 如果内存不足,请减少
    max_prompt_length=256,
    max_completion_length=200,
    # num_train_epochs=1,  # 设置为 1 进行完整的训练
    max_steps=250,
    save_steps=250,
    max_grad_norm=0.1,
    report_to="none",  # 可以使用 Weights & Biases
    output_dir=save_path,  # AutoDL推荐输出路径
)

trainer = GRPOTrainer(
    model=model,
    processing_class=tokenizer,
    reward_funcs=[
        xmlcount_reward_func,
        soft_format_reward_func,
        strict_format_reward_func,
        int_reward_func,
        correctness_reward_func,
    ],
    args=training_args,
    train_dataset=dataset,
)
trainer.train()

# 启动训练
train_result = trainer.train()

# 指定路径保存训练好的模型
trainer.save_model(save_path)

# 打印训练结果
print("训练结果:", train_result)


💡 提示:首次运行会下载ModelScope模型(~3GB),请确保有足够存储空间。训练时间约1小时(250 steps),建议使用screennohup保持会话。

在这里插入图片描述


5. 模型保存

# 保存LoRA适配器
save_path = "./outputs/02_outputs"
model.save_lora(save_path+'/grpo_saved_lora')

# 保存为ModelScope格式(可选)
model.save_pretrained(
    save_path+"/qwen-grpo-model",
    save_function=Model.save,
    tokenizer=tokenizer
)

6.模型推理

6.1 使用 GRPO 训练的 LoRA 进行推理


import torch
import warnings
from peft import PeftModel
from transformers import GenerationConfig


# 禁用 peft 的 UserWarning(关键修改)
warnings.filterwarnings("ignore", category=UserWarning, module="peft")

# 定义 SYSTEM_PROMPT
SYSTEM_PROMPT = "你是一个知识渊博、友好的助手,能准确回答各种问题。"

# 加载 LoRA 权重
model = PeftModel.from_pretrained(model, save_path+"/grpo_saved_lora")

# 应用聊天模板
text = tokenizer.apply_chat_template([
    {"role": "system", "content": SYSTEM_PROMPT},
    {"role": "user", "content": "How many r's are in strawberry?"}
], tokenize=False, add_generation_prompt=True)

# 配置生成参数
generation_config = GenerationConfig(
    temperature=0.8,
    top_p=0.95,
    max_new_tokens=1024,
)

# 将文本转换为输入张量
input_ids = tokenizer(text, return_tensors="pt").input_ids.to(model.device)

# 使用标准的 generate 方法生成输出
with torch.no_grad():
    output = model.generate(
        input_ids=input_ids,
        generation_config=generation_config
    )

# 解码输出
output_text = tokenizer.decode(output[0], skip_special_tokens=True)

print("使用监督微调的 LoRA 模型输出: ", output_text)

在这里插入图片描述


引用

[1]. https://github.com/LFF8888/FF-Studio-Resources

[2]. https://www.autodl.com/docs/

[3]. https://huggingface.co/unsloth/Qwen2.5-Coder-3B-Instruct

### GRPO 训练实践案例及相关资料 #### GRPO算法简介 GRPO(Gradient Policy Optimization)是一种强化学习中的策略优化方法,其核心在于通过梯度更新来改进策略的表现。这种算法的设计初衷是为了克服传统策略优化方法中存在的不稳定性以及收敛速度慢等问题[^1]。 #### Transformer Reinforcement Learning (TRL) 库的应用 在实际应用中,Transformer Reinforcement Learning (TRL) 是一个实现 GRPO 的重要工具库。该库不仅提供了简洁易用的接口,还允许开发者自定义奖励函数以适应不同的应用场景。例如,在自然语言生成任务中,可以通过 TRL 定义特定的语言质量指标作为奖励信号,从而引导模型生成更高质量的内容[^2]。 以下是基于 TRL 使用 GRPO 进行训练的一个基本代码示例: ```python from transformers import AutoTokenizer, AutoModelForCausalLM import torch from trl import PPOTrainer, PPOLConfig # 加载训练模型和分词器 model_name = "gpt2" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForCausalLM.from_pretrained(model_name) # 配置PPO参数 ppo_config = PPOLConfig( model_name=model_name, learning_rate=1.41e-5, batch_size=64, ppo_epochs=4, ) # 初始化PPO Trainer trainer = PPOTrainer(ppo_config, model, tokenizer) # 自定义奖励函数 def reward_fn(samples): rewards = [] for sample in samples: score = calculate_quality_score(sample) # 替换为具体的评分逻辑 rewards.append(score) return rewards # 开始训练循环 for epoch in range(num_epochs): texts = generate_texts() # 获取输入文本 model_outputs = trainer.generate(texts=texts) rewards = reward_fn(model_outputs) trainer.step(rewards) ``` 上述代码展示了如何利用 `trl` 中的 PPOTrainer 来完成一次完整的 GRPO 更新过程。其中的关键部分包括加载基础模型、配置超参数、定义奖励函数以及执行训练步骤。 #### 数据与资源开放情况分析 关于 GRPO 的具体实施细节及其配套资源,可以从 R1 GitHub 页面获取更多信息。虽然 R1 提供了 MIT 协议下的开源内容,如 GRPO 算法的技术文档和支持 API 接口的部分功能模块,但对于原始训练数据集和技术实现中的某些敏感环节并未完全公开。例如,冷启动阶段的数据准备流程、合成数据生成机制以及最终用于生产的性能调优组件均属于未开源范畴[^3]。 尽管如此,对于研究者来说,仍然可以借助已有的公共数据源(如 AI-MO 和 NuminaMath-TIR),结合官方发布的指导材料自行搭建实验环境并探索可能的方向。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值