trl_grpo_reasoning_advanced_reward.ipynb 逐格说明这份 notebook 是上一份基础 GRPO notebook 的升级版。
如果前一份在回答:
GRPO 训练最小闭环长什么样?
那么这一份在回答:
如果只靠一个 reward 不够,怎样把 RL 后训练做成一个更完整的系统?
course_lora_qwen_src 对照:输入格式、LoRA、tokenizer 这些基础骨架还在SFTTrainer 变成 GRPOTrainer!pip install transformers datasets trl bitsandbytes peft trackiobitsandbytes
trackio
这说明这份 notebook 比上一份更工程化:
这一格的作用和前面 00_check_env.ipynb 很像:
因为这份 notebook 用的是:
如果 GPU 条件不够,很容易直接 OOM。
这格把整个系统用到的组件一次性摆出来。
AutoModelForCausalLMAutoTokenizerBitsAndBytesConfigGRPOConfigGRPOTrainerLoraConfigget_peft_modelload_datasettrackio因为你现在已经进入:
model_name = "Qwen/Qwen2.5-3B-Instruct"
max_seq_length = 2048Qwen2.5-0.5B-InstructQwen2.5-3B-Instruct模型明显更大,所以后面必须引入量化。
BitsAndBytesConfig(...)这格和 Chapter2 可以直接对照。
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True,
)load_in_4bit=True
nf4
compute_dtype=torch.float16
3B 模型如果完全按普通方式加载,很多普通教学 GPU 很难稳跑。
量化是让它“装得下”的第一步。
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=bnb_config,
device_map="auto",
trust_remote_code=True,
torch_dtype=torch.float16,
)你们前面写的是:
bf16 / fp16这里是:
torch.float16device_map="auto"表示让 Hugging Face 自动决定模型放到哪里:
pad_token这一格仍然保留了和前面一致的修补逻辑:
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token说明到了 RL 阶段,这些 tokenizer 基本功仍然没有消失。
lora_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.1,
bias="none",
task_type=TaskType.CAUSAL_LM,
)配置很接近,但这里把参数说明写得更清楚了。
因为即使进入 RL 后训练:
这正是第 13 章最想让学生建立的感觉:
SFT 和 RL 后训练并不是两条完全无关的路,它们共享同一套“量化 + LoRA + tokenizer + 生成”的骨架。
这里定义了一组标记:
<start_working_out><end_working_out><SOLUTION></SOLUTION>上一份用的是:
<think><answer>这一份改成了另一套标签,但本质没变:
extract_hash_answer(text)GSM8K 的标准答案里常常带 #### 42 这种结构。
这个函数的作用就是把最终数值提出来。
process_dataset_example(example)把原始 GSM8K 样本转成:
promptanswer其中 prompt 仍然是标准 chat 结构:
[
{"role": "system", "content": system_prompt},
{"role": "user", "content": question},
]说明即使到了 RL 后训练:
dataset = load_dataset("openai/gsm8k", "main", split="train")
dataset = dataset.map(process_dataset_example)prompt 里到底有几条消息answer 是什么格式这一步和前面所有“先 print 一条样本再训练”的习惯完全一致。
这是整份 notebook 最核心的部分。
这里先把格式匹配规则编好:
因为 reward function 会被反复调用。
先编译好 regex,后面计算更快、更清楚。
match_format_exactly这个 reward 最严格。
它的作用是:
match_format_approximately这是一个更温和的 reward。
不是“全对才给分”,而是:
因为如果只有严格格式 reward,模型在早期很可能几乎一直拿不到分。
而这种“部分奖励”能给它更平滑的学习信号。
check_answer_correctness这是内容正确性的主 reward。
因为推理任务里,“接近正确”有时也值得给一点正反馈。
这比单纯的 0/1 reward 更柔和。
check_numbers_extraction这个 reward 不直接看答案全不全对,而是看:
因为推理输出里经常会出现这种情况:
这个 reward 就是在单独奖励“把最终数值说清楚”这件事。
GRPOConfig(...)这格是高级版训练的总配置中心。
learning_rate=5e-6per_device_train_batch_size=2gradient_accumulation_steps=8max_prompt_length=1024max_completion_length=1024max_steps=10max_grad_norm=0.1report_to="trackio"gradient_accumulation_steps=8和前面 Qwen 最小实验完全同类:
max_prompt_length / max_completion_length这两项在 RL 后训练里特别重要,因为:
max_grad_norm=0.1这是梯度裁剪。
RL 训练比普通 SFT 更容易不稳定,所以这里更强调控制梯度爆炸。
trackio.init(...)这一格是在做实验追踪初始化。
因为从这一格开始,RL 后训练明显更像“实验系统工程”了,而不只是一个 notebook。
GRPOTrainer(...)trainer = GRPOTrainer(
model=model,
reward_funcs=[
match_format_exactly,
match_format_approximately,
check_answer_correctness,
check_numbers_extraction,
],
args=training_args,
train_dataset=dataset,
)你现在可以明显看到:
这就是这份 notebook 的真正升级点。
这里打印了很多提示:
到了 RL 阶段,训练监控比 SFT 更重要。
因为:
trackio.show(...)启动可视化仪表盘。
帮助你看:
test_model(...)这个函数和前面 Qwen 最小实验的推理函数非常像,只是多了更细的生成参数。
temperature=0.7do_sample=Truetop_p=0.9repetition_penalty=1.1因为 reasoning 输出往往不是一个固定短答案,而是:
生成参数会明显影响输出质量和稳定性。
这一格在做最终人工检查:
因为多奖励训练的目标,最终必须回到“输出行为是否变好了”。
remove_trackio_project(...)删掉本地 trackio 数据库,避免缓存占太多空间。
torch.cuda.empty_cache() 和 gc.collect()这是训练后清理 GPU 和 Python 内存的标准习惯。
这也说明: