Day21 | GRPO 实战——从零跑通一个能解数学题的 reasoning 模型 苦猿的大模型日记 · Day21 · GRPO 实战——从零跑通一个能解数学题的 reasoning 模型-帮普通人把AI学进简历系列前言那个 reward 死活不涨的凌晨我给你讲个事。上个月某个周末凌晨一点我对着 SwanLab 上的一条平直红线发呆。那条线叫reward/mean理论上是 GRPO 训练里最重要的指标——它该是波动的、上升的、有生气的。可它从第 1 步到第 50 步纹丝不动地贴在 -2.0 附近像死人的心电图。我调的是 Qwen3-8B跑 GRPO数据集是 DAPO-Math-17k。模型加载正常、reward 函数能编译、训练循环能跑——可模型就是不学。第一反应reward 函数写错了。我把check_answer拆开一行行看没问题。第二反应数据集 ground truth 有问题。我把dataset[0][solution]打印出来对得上。第三反应——lr 太小。我把学习率从5e-6提到5e-5reward 还是 -2.0 一条直线。折腾到凌晨三点半我才反应过来——是start_working_outend_working_outSOLUTION这三个特殊 token 没加进 tokenizer。apply_chat_template我调了chat template 拼得也对prompt 里清清楚楚写着按start_working_out...end_working_outSOLUTION.../SOLUTION输出——可模型压根没把这些尖括号字符串当成特殊标记。在它眼里SOLUTION跟hello一样是普通字符于是输出时该写SOLUTION的位置它写成 solution SOLUTION SOL……各种花样。而我的 reward 函数靠正则严格提取SOLUTION标签——错一个字符都提取不到check_answer直接返回 -2.0格式错check_numbers返回 -2.5提取不到数值。所以 group 内 8 条回答 reward 全是 -2.0 / -2.5 这类负分几乎没区分度advantage 长期接近 0梯度近乎为 0。模型什么都学不到。那一刻我突然想明白一件事——GRPO 难的根本不是算法是 reward 函数和你模型实际输出能不能对上电波。上一篇我讲 GRPO 理论的时候把算法公式、group baseline、三大改造讲了一遍。今天这篇我给你一把铲子——从零到一跑通一个能解 GSM8K 数学题的 Qwen3-8B。跑完之后你会发现公式那张纸只占 10% 的工作量。剩下 90%——全是 chat template、reward 函数、reward 又不涨、KL 又爆炸这种工程脏活。而面试官最爱问的恰恰就是这 90%。PART 01为啥必须先 SFT 再 GRPO——R1 路线的真相很多人对 DeepSeek-R1 有个误解——R1-Zero 不是直接对 base 模型跑 GRPO 就行吗是的那是 72B 2048 张 H800 集群的暴力美学。你拿来跑 8B 单卡百分之百不收敛。为什么因为 base 模型根本不会按start_working_out...end_working_outSOLUTION.../SOLUTION这种格式输出。它的输出是自然语言大白话。而 GRPO 的 reward 函数靠正则提取SOLUTION标签——base 模型不说这种话reward 函数全返回负的固定值-2.0 / -2.5group 内 8 条几乎没区分度。advantage 长期接近 0梯度近乎为 0——模型什么都学不到。这就是为什么 self-llm 的官方教程、Unsloth 的官方 notebook全都把训练分成两阶段SFT 阶段教模型说话的格式SFT 阶段的目的不是教模型会做题——这一点很关键。目的是教模型按固定的格式说话start_working_out 思考过程 end_working_out SOLUTION 最终答案 /SOLUTION只要模型稳定地输出这种格式SFT 就成功了。会不会做题那是 GRPO 阶段的事。GRPO 阶段教模型做对题格式对了之后reward 函数才能稳定提取答案、给奖励。这时候 GRPO 才开始有意义——模型通过试错学会什么样的思考链能拿高分。数据集选择SFT 阶段unsloth/OpenMathReasoning含完整 CoT 推理链2025 reasoning 训练的事实标准GRPO 阶段open-r1/DAPO-Math-17k-ProcessedDeepSeek 开源 R1 路线的官方数据集处理版两个数据集的设计目的不一样——SFT 用的是带完整推理链的标准答案GRPO 用的是只有最终答案的题目。前者教格式后者逼推理。SFT 配置——别跑多trainer SFTTrainer( model model, processing_class tokenizer, train_dataset dataset, args SFTConfig( per_device_train_batch_size 1, gradient_accumulation_steps 1, warmup_steps 5, num_train_epochs 2, # 别跑多2 epoch 够学格式 learning_rate 2e-4, # 比 GRPO 大一个量级 optim adamw_8bit, weight_decay 0.01, lr_scheduler_type linear, report_to swanlab, ), )几个反直觉点num_train_epochs2很多人觉得 SFT 该跑 5-10 epoch 把 loss 压下去。这里不能多跑——多跑了模型 CoT 风格僵化反而不利于 GRPO 阶段探索learning_rate2e-4比 GRPO 阶段大两个数量级。LoRA 训练有快进快出的特点lr 小了学不动per_device_train_batch_size18B 模型在单卡上的极限。靠gradient_accumulation_steps凑等效 batchSFT 跑完你应该能看到 loss 从 1.5 左右下降到 0.5 以下。这时候模型已经稳定按格式输出了可以进 GRPO。如果你 SFT loss 卡在 1.0 以上不下降——先回头查数据预处理别急着进 GRPO。PART 02模型加载——Unsloth 怎么把 8B 塞进单卡我上一篇讲 GRPO 理论的时候硬件门槛写的是4 卡 A100 80G 起步。那是没用 Unsloth 之前的乐观估计。2025 下半年开始Unsloth 把 Qwen3-8B GRPO 的显存账砍到让人发指——单卡 H100 80GQLoRA 路线能跑 Qwen3-8B GRPOcontext 拉到 110K。Unsloth 的三板斧Flash Attention 2/3注意力层显存砍掉一半以上QLoRA 4bit 量化8B 权重从 BF16 的 ~16GB 压到 ~5GBgradient_checkpointingunsloth用算力换显存反向传播时重算激活值三板斧下来80G 卡跑 GRPO 还能留出 30-40G 给 vLLM 采样——这是 GRPO 显存真正的吃大户。加载代码——三个最容易踩的坑下面这份是SFT 阶段的加载方式GRPO 阶段要改load_in_4bit下面坑①讲from unsloth import FastLanguageModel import torch max_seq_length 2048 lora_rank 32 model, tokenizer FastLanguageModel.from_pretrained( model_name Qwen/Qwen3-8B, max_seq_length max_seq_length, load_in_4bit False, # 坑①SFT 阶段 False / GRPO 阶段改 True fast_inference True, # 坑② max_lora_rank lora_rank, gpu_memory_utilization 0.7, # 坑③ ) model FastLanguageModel.get_peft_model( model, r lora_rank, target_modules [ q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj, ], lora_alpha lora_rank * 2, use_gradient_checkpointing unsloth, random_state 3407, )坑①load_in_4bit——SFT 和 GRPO 该填不一样的值SFT 阶段可以False——没有 vLLM 采样FP16 权重 ~16G 在 80G 卡上还能挤得下GRPO 阶段必须改True——vLLM 要吃掉 30-40G 显存采样 group 内 8 条回答QLoRA 帮你把权重从 16G 砍到 5G否则单卡必 OOM进 GRPO 前要重新from_pretrained加载一次别想直接复用 SFT 阶段的 model 对象坑②fast_inferenceTrue开vLLM 加速推理——GRPO 每个 step 要采样 8 条回答group size G8不开 vLLM 你等到天荒地老Qwen3-8B 当前 Unsloth 版本下单卡训练必开版本坑容易翻车Unsloth 新版本对部分新模型如 Qwen3.5 系列的 GRPO 实际上推荐fast_inferenceFalse走 Unsloth 原生 inference——因为 vLLM 对新模型支持有滞后。遇到 vLLM 报 model not supported 类错误时第一反应是把fast_inference改False坑③gpu_memory_utilization0.7vLLM 占显存的比例。太高训练 forward 会 OOM太低采样慢0.7 是 self-llm 教程的默认值实测在 H100 80G 上稳定LoRA 配置——r 不是越大越好我跑 GRPO 试过r8、r32、r64——r8显存小但 GSM8K 这种任务欠拟合r32甜点尺寸GSM8K 已饱和r64显存翻倍acc 没明显提升target_modules必须全套投影矩阵都挂——q/k/v/o 注意力层 gate/up/down 前馈层。只挂 q/v 是那种能跑就行的偷懒配置reasoning 任务顶不住。lora_alpha lora_rank * 2是社区惯例——α 控制 LoRA 输出对原始权重的扰动强度2r 是个稳的经验值。PART 03reward 函数设计——GRPO 真正的技术含量到这里我们终于要碰到 GRPO 训练最核心的东西了——reward 函数。我上一篇讲理论的时候说过GRPO 的算法不复杂真正难的是 reward 函数。今天给你看 self-llm 教程的四件套——这是 2025 下半年到 2026 中文社区事实标准的写法。4 个 reward 函数协同self-llm 给的reward_funcs列表是这样的trainer GRPOTrainer( model model, reward_funcs [ # match_format_exactly, # 注释掉了理由后面讲 match_format_approximately, # 宽松格式奖励 check_answer, # 答案正确性 proximity reward check_numbers, # 纯数值兜底 ], ... )4 个函数各司其职最终 reward 是它们的加权和。一个一个看。函数①match_format_exactly被注释掉了严格匹配end_working_outSOLUTION.../SOLUTION格式——二值奖励命中 1 / 不命中 0。self-llm 教程把这个函数注释掉了。为什么因为它的功能被宽松版完全覆盖。你写两个会冗余而且严格匹配在前 100 步模型还没学会格式时全是 0——没梯度反而拖累训练。这是一个工程经验reward 函数不是越多越好。函数②match_format_approximately宽松格式def match_format_approximately(completions, **kwargs): scores [] for completion in completions: score 0 response completion[0][content] # 每个标签该出现 1 次命中 0.5多次出现 -1.0 score 0.5 if response.count(reasoning_end) 1 else -1.0 score 0.5 if response.count(solution_start) 1 else -1.0 score 0.5 if response.count(solution_end) 1 else -1.0 scores.append(score) return scores为什么需要宽松格式奖励一开始模型还没学会格式严格奖励全是 0 没梯度——宽松奖励给接近正确的部分分。模型哪怕只出现了SOLUTION开标签也给 0.5 分鼓励一下。这是 reward shaping 的核心思想给对的信号也给接近对的信号。函数③check_answer本篇最核心def check_answer(prompts, completions, answer, **kwargs): responses [c[0][content] for c in completions] extracted [ guess.group(1) if (guess : match_format.search(r)) else None for r in responses ] scores [] for guess, true in zip(extracted, answer): if guess is None: scores.append(-2.0) # 格式错扣分但别太狠 continue if guess true: scores.append(5.0) # 完美命中 elif guess.strip() true.strip(): scores.append(3.5) # 空格差异 else: # proximity reward——这是 R1 路线的精髓 try: ratio float(guess) / float(true) if 0.9 ratio 1.1: scores.append(2.0) elif 0.8 ratio 1.2: scores.append(1.5) else: scores.append(-2.5) except: scores.append(-4.5) # 非数值最狠的惩罚 return scores这是整篇 GRPO 训练里最该拆透的函数。几个关键设计——完全正确 5.0 / 去空格正确 3.5这是基础奖励。proximity reward数值近似奖励这是 R1 路线最精髓的设计——模型答案接近正确答案也给部分分答案在 ±10% 内2.0答案在 ±20% 内1.5答案差距大-2.5为什么 proximity 这么重要GRPO 的 group baseline 机制下——如果 group 内 8 条回答全错hard 题模型完全不会做reward 全是 -4.5 或 -2.5advantage 全是 0梯度直接消失模型什么都学不到。加了 proximitygroup 内的 8 条错误回答开始有区分度——差 5%的拿 2.0、差 30%的拿 -2.5、完全跑偏的拿 -4.5。哪怕全错模型也能从哪个错得更接近里学到东西梯度信号不至于塌缩。这是 reasoning 任务用 proximity reward 的真正价值——它不是为了鼓励差一点对是为了在 group 全错时保留梯度信号。格式错 -2.0 / 非数值 -4.5负 reward 不可怕可怕的是全 0。负 reward 至少给模型一个别这么干的信号。函数④check_numbers数值兜底check_answer的孪生兄弟专门处理SOLUTION标签内纯数字的情况还兼容了1,234这种带逗号的数字。guess float(guess.strip().replace(,, ))这种兜底看着不起眼但 GSM8K 里大量答案是大数字——没有replace(,, )1,234 会直接抛异常进入except分支被打 -4.5 分模型委屈死。reward 函数的心法写完这 4 个函数你回头看——reward 函数不是越复杂越好也不是越严格越好。self-llm 把严格匹配注释掉、宽松匹配留 3 个标签各 0.5 分、proximity reward 给接近的部分分——所有设计都在围绕一件事让梯度信号密集起来。GRPO 靠 group baseline 算 advantage。组内 reward 全相等时 advantage0梯度直接消失——这是 GRPO 最阴险的崩盘点。你的 reward 函数必须保证——同一道题的 8 条采样回答里reward 要有区分度。PART 04GRPOTrainer 配置——一个 step 里到底跑什么reward 函数准备好进 GRPOTrainer。训练参数——每个超参为啥这么设from trl import GRPOConfig, GRPOTrainer training_args GRPOConfig( output_dir outputs, num_generations 8, # group size G max_prompt_length 512, max_completion_length 1024, per_device_train_batch_size 1, gradient_accumulation_steps 1, learning_rate 5e-6, # 比 SFT 小两个数量级 temperature 0.9, # 采样多样性来源 beta 0.04, # KL 系数default loss_type dr_grpo, # DAPO/R1 路线推荐的 loss 变体 logging_steps 5, optim adamw_8bit, weight_decay 0.01, lr_scheduler_type linear, report_to swanlab, ) trainer GRPOTrainer( model model, processing_class tokenizer, reward_funcs [ match_format_approximately, check_answer, check_numbers, ], args training_args, train_dataset dataset, ) trainer.train()每个超参的为什么——num_generations8group size G上一篇讲过G 太小 baseline 不稳G 太大显存爆G8 是 DeepSeek 论文的默认值单卡 H100 上稳跑调高 G16 的话每个 prompt 要采样 16 条回答vLLM 显存吃紧max_prompt_length512 / max_completion_length1024reasoning CoT 普遍 800 tokenmax_completion_length别压到 512 以下——硬截断会直接砍掉模型的SOLUTION标签check_answer提取不到答案打 -2.0形成越长越被截断越被扣分的恶性循环prompt 通常 200 token 内512 留富余per_device_train_batch_size1GRPO 显存敏感单卡极限就是 1别想着开 4——一个 step 直接 OOMlearning_rate5e-6比 SFT 小两个数量级reasoning 的 reward 噪声大lr 一大 reward 曲线直接发散调到1e-5试试可以再大就崩beta0.04KL 系数TRL 默认值控制 policy 漂离 reference 多远KL 一直稳定 0.1可以调低到 0.01 让模型更激进探索KL 爆炸了调高到 0.1 拉紧loss_typedr_grpoDAPO/R1 路线推荐的 loss 变体Dr. GRPO去掉了 GRPO 原版一个偏差项默认的grpo也能跑但dr_grpo在数学推理上效果略好temperature0.9太低 0.7group 内 8 条回答全一样reward 方差为 0崩盘太高 1.2模型胡言乱语格式都保不住0.9 是 self-llm 教程的默认实测稳一个 step 究竟跑了什么代码贴完了。给你看一个 step 内部干了啥——采样 prompt从数据集取 1 个数学题vLLM 并行采样 8 条回答temperature0.9保证多样性这步是显存大头reward 函数并行算分每条回答得到一个总分你前面写的 3 个函数加权和算 group advantageA_i (reward_i - mean) / std一次前向算两个 logprobLoRA 路线下的精髓——enable adapter 算 policy logprobdisable adapter 算 reference logprob前向只用跑一次算 KL 损失beta * KL(policy || reference)beta默认 0.04反向传播 优化器 stepclipped objective KL 一起进 loss更新 LoRA 权重注意第 5 步——LoRA 路线下不需要你单独加载 reference model。GRPOTrainer内部 disable adapter 就拿到 reference logprob这是 LoRA 路线相对全量微调的隐藏优势全量微调要额外存一份 reference 权重。这也是为什么前面强调gpu_memory_utilization0.7——vLLM 采样和训练 forward 共享 GPU留少了 OOM留多了训练慢。PART 05训练循环监控——三大崩盘点怎么救跑起来只是开始。真正决定你能不能跑出来的是看监控。我跑 GRPO 踩过的三大崩盘点——一个一个给你拆。崩盘点①reward 死活不涨贴在某个固定值症状reward/mean从第 1 步到第 50 步纹丝不动地贴在某个固定值上0 附近、-2.0 附近、或者 -4.5 附近都见过。就是我开篇讲的那个故事。大概率原因chat template 没用apply_chat_template模型没按格式输出SFT 阶段没跑或没跑好base 模型根本不会按格式说话reward 函数的正则和模型实际输出格式对不上该动哪个旋钮第一步打印前 5 条采样回答肉眼看——for i, r in enumerate(responses[:5]): print(f Response {i} ) print(r)如果看到的是自然语言大白话、没有SOLUTION标签——回去修 SFT 或修 chat template。崩盘点②奖励信号塌缩症状reward/std为 0reward/mean要么是 5 要么是 -4.5——group 内 8 条回答全对或全错。为啥是崩盘group baseline 算 advantage 时组内 reward 全相等 → advantage0 → 梯度为 0 → 模型什么都学不到。大概率原因数据集太简单模型已经会做8 条回答全对数据集太难模型一个都做不出8 条回答全错temperature太低采样出来的回答都长得一样该动哪个旋钮换数据集难度分布——DAPO-Math-17k 已经按难度分层直接用就行temperature调高到 1.0-1.2——让 group 内有多样性崩盘点③KL 散度爆炸症状kl_loss指数级上升模型输出开始乱码、出现非英文符号、答非所问。大概率原因learning_rate太大——这是 90% 的情况betaKL 系数太小——GRPOConfig默认beta0.04太松会导致 policy 漂离 reference 太远极少数情况LoRA adapter 的 target_modules 不全漏挂了gate/up/down_proj导致 reference 和 policy 的 logprob 计算路径不一致该动哪个旋钮lr 减半从5e-6降到2e-6把beta从0.04调到0.1拉紧 KL 这根绳不要去手动加载/冻结 reference model——GRPOTrainer内部用 LoRA 的 disable adapter 技巧算 reference logprob前向一次disable adapter 拿到 reference、enable adapter 拿到 policy你手动加 reference_model 反而会和 trainer 打架报错一个常被忽略的信号completion_length监控 4 条曲线里最容易被忽略的是completion_length。健康的 GRPO 训练completion_length应该缓慢上升——模型学会写更长更细的 CoT是 reasoning 训练有效的关键信号。如果completion_length下降——模型在偷懒写越来越短的回答来骗 reward。这时候要检查 reward 函数——是不是有某个函数的奖励偏好短回答。比如你的 format reward 写成了越短奖励越高模型就会钻空子。崩盘点对照表建议收藏症状大概率原因该动哪个旋钮reward mean 死活不涨chat template 没对 / 特殊 token 没加进 tokenizer / SFT 没跑打印采样肉眼看回头修 SFT 或 tokenizerreward std 长期 0group 内全对或全错换数据难度 / 调高 temperatureKL 指数级上升lr 太大 / beta 太松lr 减半 / beta 从 0.04 调到 0.1completion_length 下降reward 函数偏好短回答检查 reward 设计加长度惩罚completion_length 持续顶到 maxmax_completion_length太小CoT 被截断调大到 1536/2048被截断的回答SOLUTION出不来check_answer全打 -2.0会形成模型越写越长 → 越被截断 → reward 越负的恶性循环loss 突然变 NaNreward 数值过大 / lr 过大加 reward clipping / lr 减一个数量级显存 OOMgroup 太大 / context 过长降 num_generations 或 max_completion_length心法——GRPO 调参的本质是同时管三个变量数据难度分布 / reward 函数权重 / 采样温度。但比 PPO 还是简单一截。因为这三个变量的因果关系清晰得多——不像 PPO 那样 lr / β / KL 互相纠缠。PART 06推理验证——把训好的 LoRA 跑一道真数学题训完了得验证。保存 LoRA 权重model.save_lora(grpo_saved_lora)LoRA 权重只有几十 MB——比 SFT 全量权重小两个数量级可以随便拷。加载 LoRA 推理from vllm import SamplingParams # 切到推理模式——FastLanguageModel 底层把 LoRA adapter 挂到 vLLM 上 FastLanguageModel.for_inference(model) text What is the sqrt of 101? messages [ {role: system, content: system_prompt}, {role: user, content: text}, ] sampling_params SamplingParams( temperature 0.8, top_p 0.95, max_tokens 2048, ) output model.fast_generate( [tokenizer.apply_chat_template(messages, tokenize False)], sampling_params sampling_params, )[0].outputs[0].text注意max_tokens2048——reasoning 模型推理时 CoT 经常写到 1500 token设小了SOLUTION标签出不来你会以为模型坏了。真实对比SFT 后 vs GRPO 后我跑过同一道 GSM8K 题对比如下——SFT 后的输出短start_working_out 这道题要求小明有多少个苹果。 他原来有 5 个妈妈又给了 3 个。 5 3 8 end_working_out SOLUTION 8 /SOLUTIONGRPO 后的输出长start_working_out 这道题要求小明有多少个苹果。 他原来有 5 个妈妈又给了 3 个。 5 3 8 等等让我检查一下——题目里妈妈又给了 3 个是不是有别的意思 重新读题妈妈又给了 3 个红苹果和 2 个绿苹果。 哦我漏了。3 2 5。 所以总共是 5 5 10。 让我再算一遍。 原有 5 红色 3 绿色 2 10。 end_working_out SOLUTION 10 /SOLUTIONGRPO 之后模型的 CoT明显变长变细会自我检查——等等让我重新算一遍这种话是 reasoning 模型的典型特征。别只看最终答案对不对GSM8K accuracy 提升可能只有 5-10%——但你不能因此说 GRPO 没用。reasoning 训练的 ROI 要看思考过程——CoT 长度上升、出现自我检查、能处理多步推理——这才是 reasoning 模型的价值。如果你只看 accuracy 卡死建议测一下AIME / MATH 这种高难度数据集提升幅度会大得多。导出 GGUF可选model.save_pretrained_gguf(grpo_gguf, quantization_method q4_k_m)可以丢到 llama.cpp / Ollama 跑——这是我下一篇要讲的事先挖坑。结尾GRPO 实战门槛在工程不在算法到这里从 SFT 冷启动到 4 个 reward 函数再到 GRPOTrainer 配置、监控崩盘点、推理验证——全链路跑通了。我猜你已经发现一件事——GRPO 真正难的地方没有一处是算法。group baseline 的公式你不用手写——TRLGRPOTrainer帮你算了。clipped objective 你不用手写——还是 TRL 帮你算了。你真正花时间的地方是——chat template 该怎么拼reward 函数的正则能不能匹配上模型实际输出group 内 8 条采样有多样性还是全一样KL 在第 200 步突然爆炸怎么办vLLM 显存留 0.7 还是 0.6 才不 OOM全是工程脏活。这就是为什么我开篇那句话——GRPO 难不在算法难在 reward 函数和你模型实际输出能不能对上电波。简历视角——三层答案面试官问你跑没跑过 GRPO你能答出三层——跑过能讲清楚 SFT 冷启动为什么必要——base 模型不会按格式输出reward 函数提取不到答案reward 函数怎么设计——4 类 reward proximity 数值近似让梯度信号密集崩盘点怎么救——reward 不涨 / 信号塌缩 / KL 爆炸三大症状的诊断逻辑这三层答下来面试官知道你不是看了篇博客就来面的那种人——你是真在凌晨三点半对着 reward 不涨发过呆的人。一点观察国内中文社区 GRPO 实战教程现在主要靠self-llm / Unsloth / SwanLab三个项目撑着。self-llm 是 datawhale 出的中文开源大模型食用指南Unsloth 是 Daniel Mike Han 维护的显存优化训练框架SwanLab 是国产的训练追踪/可视化平台对标 WandB国产 LLM 圈用得最广——这三个组合在 2025 下半年到 2026 基本是中文社区 GRPO 入门的事实标准栈。三个月内能把这套跑通简历上GRPO 实战经验这一栏就有底气了。下一篇预告——reasoning 训练这块到今天告一段落。下一步两个方向我都很想写——A多模态CLIP / VLM / 多模态 RAGB评估LM-Eval / 魔搭榜 / 自己搭一套评估 pipeline评论区投票吧哪个票多先写哪个。互动时间——你跑 GRPO 卡在哪是 reward 死活不涨还是 KL 突然爆炸还是 reward 函数写得想砸键盘评论区聊聊下一篇我会挑几个典型问题在开头回复。最后送你一句——RL 训练的真相是10% 的时间在想算法90% 的时间在调 reward 函数。论文写得很优雅工程现场全是脏活。而面试官要的恰恰是那个在脏活里爬出来的人。— END —苦猿 · 帮普通人把 AI 学进简历