变分参数探索:破解RLVR稀疏奖励难题的PyTorch实践
做 LLM 后训练的同学可能都有过类似的困扰奖励模型越训越飘偏好标注成本高PPO 的超参数一调就是好几天模型还动不动就 reward hacking。最近 RLVRReinforcement Learning with Verifiable Rewards带可验证奖励的强化学习成了后训练阶段的热门方案它用规则、编译器、标准答案直接打分绕开了奖励模型训练中的很多问题。但 RLVR 也有自己的硬伤——奖励非常稀疏探索一旦不到位策略就会卡在局部最优甚至彻底塌缩。本文围绕一个偏学术、但工程上也很有价值的切入点展开如何通过变分学习在参数空间做探索并给出一个可运行的 PyTorch 最小示例。读完你会明白参数探索和动作探索的本质区别也能在自己项目里复现一套最简 RLVR 变分参数探索的基线为后续扩展到 LoRA 子空间探索打基础。1. 背景RLVR 为什么值得关注1.1 从 RLHF 到 RLVR传统的大模型后训练路线是 RLHFReinforcement Learning from Human Feedback。它的基本流程是先收集人类偏好数据训练一个奖励模型Reward Model再用强化学习算法通常是 PPO让策略模型去最大化这个奖励模型的分数。问题也随之而来奖励模型本身就是对真实偏好的近似它可能被策略模型钻空子也就是 reward hacking另外奖励模型的训练和维护成本都很高人工标注偏好数据的质量也直接影响最终效果。RLVR 的思路则完全不同它不需要训练奖励模型而是直接使用可验证的奖励函数。比如数学题可以直接比对最终答案是否正确代码题可以直接跑测试用例或编译逻辑推理题可以校验结论与规则是否一致。奖励信号不再来自一个学习出来的模型而是来自程序化、规则化的验证器。这样做的好处非常明显奖励更干净、可解释、可复现也不会出现奖励模型被对抗性利用的问题。以 DeepSeek-R1 为代表的一批开源模型在训练中引入了 RLVR 思路带火了这个方向。公开资料中常见的做法是像 GRPOGroup Relative Policy Optimization那样对同一个问题采样一组响应然后用组内相对优势代替独立的 critic 网络来估计优势函数。这类方法把 RLVR 的工程门槛降低了不少也让更多团队开始在后训练里尝试这种规则打分 强化学习的组合。1.2 可验证奖励的定义与典型场景可验证奖励Verifiable Reward的核心特征是给定一条生成结果我们可以用确定性的规则判断它好不好而不需要人或者模型主观打分。最典型的几类场景包括数学与推理题模型输出最终答案验证器将答案与标准答案做精确匹配或规范化匹配。代码生成模型生成代码验证器执行单元测试通过用例数量或编译结果作为奖励。指令遵循与格式校验模型输出是否满足结构化协议比如 JSON 合法性、字段完整性。组合优化与搜索类问题判断模型给出的解是否满足约束条件并计算目标函数值。这类奖励通常是稀疏的对了得 1 分错了得 0 分或者最多给一个部分得分。稀疏奖励会带来一个非常现实的困难——如果策略在动作空间里没有充分探索它可能长期收集不到正反馈模型不知道该往哪个方向优化。尤其是训练初期策略模型可能只会输出少数几种固定模式而可验证奖励又只关心最终结果这就导致初期训练信号几乎为零。1.3 RLVR 训练中的探索难题在 RLVR 场景里探索难题会被进一步放大。原因在于动作空间是高维的 token 空间而可验证奖励只落在很窄的正确答案区域上。如果模型一开始生成的都是些离正确方向很远的内容奖励一直为 0那么所有梯度都指向降低错误答案的概率模型很容易陷入一种保守化坍缩开始大量输出安全但毫无意义的短答案比如空字符串、重复符号或者固定的兜底句式。传统的动作探索策略比如在 token 采样时增加温度、引入熵正则、对动作施加噪声往往是在给定策略参数的条件下让动作分布更随机。这种探索本质上仍然受限于当前策略参数所限定的行为集合。如果策略参数本身已经把某些行为模式的概率压到了极低动作层面的随机扰动很难把这些模式重新激活。这时候就需要把探索的粒度提升到参数层面也就是本文接下来要重点讨论的参数探索Parameter Exploration。2. 动作探索与参数探索的本质差异2.1 动作空间探索的常用手段与局限强化学习里最经典的探索手段几乎都是作用在动作空间的。epsilon-greedy 是让智能体以一定概率随机选取动作熵正则是在策略目标中加上动作分布的熵项鼓励策略不要过早收敛成确定性分布NoisyNet 是在网络输出的动作或 Q 值上叠加噪声。这些方法在连续控制、游戏博弈等场景中表现不错因为它们面对的环境通常能提供较为稠密的过程奖励随机探索很快就能试出有效行为。但在 RLVR 中动作空间探索的局限很明显。第一token 级随机探索效率低在巨大的词汇表上随机采样命中正确答案的概率极低。第二熵正则只是让概率分布平坦一些它改变的是输出概率的均匀程度而不是策略的底层参数结构因此难以产生结构性的行为变化。第三动作探索的信号通常是单步的而 RLVR 关心的是整段生成的最终验证结果单步动作层面的扰动很难和最终稀疏奖励建立有效的信用分配。2.2 参数空间探索的思路参数空间探索的思路是不直接在动作上做文章而是让策略参数本身服从一个随机分布。每次训练时先从参数分布中采样一组参数用这组参数生成完整轨迹再根据可验证奖励决定这组参数是应该被奖励还是被惩罚进而调整参数分布的均值和方差。这个思想在经典强化学习中并不陌生。进化策略Evolution Strategy就是典型的参数探索方法维护一个参数分布反复采样参数、评估适应度、按适应度更新分布。NoisyNet 虽然在实现上更像动作噪声但也包含了对网络参数施加噪声的思想。Bayesian Policy Gradient、Variational Policy Gradient 等变分方法则把参数探索纳入到一个更严格的概率建模框架中用变分推断来学习参数的后验分布。参数探索对 RLVR 的价值在于它能一次性产生一群行为差异很大的候选策略。只要某一组采样参数碰巧生成了正确答案优化器就能把整个参数分布向这组参数的方向拉近。这比在一个固定策略上做 token 级随机采样要广得多也更符合验证结果驱动改进的 RLVR 风格。2.3 参数探索如何缓解奖励稀疏奖励稀疏的本质是正样本太少而参数探索可以理解为一种并行随机搜索 定向更新的机制。采样的 K 组参数相当于同时尝试了 K 个不同的行为策略它们之间的差异不只是某个 token 的选择而是整个行为模式的切换。只要 K 足够大且参数分布没有过早坍缩那么至少有一组采样参数有概率靠近正确答案区域。配合组内基线Group Baseline参数探索还能缓解奖励尺度不一致的问题。同一个问题下一组采样参数的平均奖励可以当作基线高于基线的参数被推高概率低于基线的参数被压低概率。这种相对比较的思想和 GRPO 恰好同源。把参数探索和组内基线结合既能利用组内对比带来的稳定优势估计又能获得参数级探索带来的行为多样性两者是互补关系。3. 变分学习的核心原理3.1 变分推断的通俗解释变分推断Variational Inference是一类用优化代替采样做贝叶斯推断的方法。假设我们想求参数的后验分布 p(θ | D)但真实后验往往无法解析计算。变分推断的做法是找一个参数化的近似分布 q(θ; φ)通过优化 φ 让 q 尽量逼近真实后验。放在 RLVR 场景里这个思路可以重新解释为我们并不非要先有大量数据再推断后验而是直接把策略参数当作随机变量用一个高斯分布 q(θ; μ, σ²) 来刻画哪些参数更可能带来高奖励。训练过程就是不断调整 μ 和 σ²让这个分布向高奖励参数区域靠拢。如果 σ 变小说明我们越来越确信某个参数区域是好的如果 σ 保持较大说明还在保留探索空间。3.2 ELBO 与重参数化变分推断的核心优化目标是证据下界ELBO。具体形式有很多写法但核心结构是两项之和第一项是期望对数似然鼓励近似分布去解释数据第二项是负的 KL 散度鼓励近似分布不要偏离先验太远。也就是说模型既要在数据拟合上足够好又要保持一定的正则约束。要让 ELBO 可以用梯度下降优化通常需要用到重参数化技巧Reparameterization Trick。对于高斯分布我们可以把从 N(μ, σ²) 采样改写成先从标准正态采样 ε再计算 θ μ σ ⊙ ε。这样一来随机性被转移到了与参数无关的 ε 上而 θ 关于 μ 和 σ 就是确定可微的函数梯度可以正常反向传播。这个技巧是 Bayes by Backprop、变分自编码器等方法的基石也是本文示例代码中采样参数的基础。3.3 从变分推断到变分策略学习把变分推断用在策略参数上就得到变分策略学习Variational Policy Learning的基本框架。我们不再维护一组固定的网络权重而是维护权重分布 q(θ; μ, σ²)。每次前向传播前先从分布中采样一组权重再用采样到的网络与环境交互收集轨迹和奖励。这里有两点需要特别注意。第一参数分布的维度和网络参数量一致如果网络很大直接为每个权重维护 μ 和 log σ² 的代价是双倍显存和计算量。因此实际工程中通常只在低秩子空间、LoRA 适配器参数或部分层上做参数探索。第二变分策略学习中存在两种梯度估计方式一种是通过采样参数的 log q(θ) 做 score function 估计另一种是通过重参数化路径把奖励梯度直接传回 μ 和 σ。前者实现简单但方差大后者方差小但对奖励函数的可微性有一定要求。在 RLVR 这种奖励来自规则验证器的场景里奖励完全不可微因此本文示例采用 score function 组内基线的方案。4. RLVR 变分参数探索的算法设计4.1 整体目标函数假设策略参数 θ 服从参数化分布 q(θ; φ)其中 φ 表示 μ 和 log σ²。我们用可验证奖励函数 R_v(θ) 表示从分布中采样出参数 θ 后用该策略在环境中获得的平均可验证奖励。优化目标可以写成一个带 KL 正则的期望奖励最大化问题max_φ E_{θ ~ q(θ; φ)} [ R_v(θ) ] - β * KL( q(θ; φ) || p(θ) )其中 p(θ) 是先验分布一般取标准正态 N(0, I)。KL 项的作用是防止参数分布过度集中在某个点上从而保留探索能力β 控制正则强度。如果 β 太大分布会被强拉向先验学习变慢如果 β 太小分布可能过早坍缩探索能力下降。由于 R_v(θ) 来自规则验证器不可微我们需要用采样估计梯度。上式的梯度可以用 score function 估计为∇_φ J ≈ (1/K) * Σ_{k1..K} (R_v(θ_k) - b) * ∇_φ log q(θ_k; φ)其中 θ_k 是从 q 中采样得到的 K 组参数b 是组内基线通常取 K 组奖励的均值。减掉基线不会改变期望梯度的方向但能显著降低方差这和策略梯度里使用 baseline 的原理一致。4.2 算法流程结合 RLVR 的验证流程整个算法可以拆成下面几个步骤初始化参数分布 φ (μ, log σ²)通常 μ 随机初始化或从基础模型导出log σ² 初始化为 0 附近。从 q(θ; φ) 中采样 K 组参数 θ_1, ..., θ_K。对每一组参数 θ_k运行策略生成轨迹。在大模型场景下就是让模型生成 K 组回答或代码。使用可验证奖励函数对每组生成结果打分得到 R_1, ..., R_K。计算组内基线 b mean(R_k)并计算优势 A_k R_k - b。计算 log q(θ_k; φ) 对 φ 的梯度并按 score function 公式更新 φ同时加上 KL 正则项梯度。重复步骤 2 到 6直到验证集奖励收敛或达到预设步数。这个流程和 PPO、GRPO 的生成-评估-更新三步结构非常接近区别只在于更新对象不再是固定的策略网络权重而是参数分布的均值与方差。如果后续想引入重要性采样或 clip 机制也可以把 φ 的更新限制在一个信任区域里进一步提升稳定性。4.3 为什么用组内 baseline在 RLVR 中不同问题的绝对奖励尺度可能差异很大。有些问题所有候选回答都答对了有些问题全部答错。如果用全局奖励作为训练信号模型可能偏向那些容易得分的问题类型而忽略困难问题。组内 baseline 恰好解决了这个问题对同一个问题我们只看这组采样参数中哪些相对更好哪些相对更差再做优势加权。组内 baseline 还有另一层好处它天然抵消了奖励中的公共偏移。假设所有采样参数在某个问题上的奖励都整体较高baseline 也会高优势会被拉回零附近不会形成过度自信的更新反之如果整体较低baseline 低相对好的参数仍然能获得正优势。这跟 GRPO 使用的组内相对优势思想是一致的也是把参数探索和 RLVR 结合时比较稳定的工程选择。5. 最小可运行示例PyTorch5.1 示例任务设计为了让代码足够简单又能体现可验证奖励 参数探索的核心逻辑我设计了一个极简的上下文多臂任务输入状态 s 是 0 或 1模型需要输出动作 a 等于 s 才能获得奖励。验证规则很简单状态等于动作得 1 分否则得 0 分。策略是一个线性层从 one-hot 状态编码映射到两个动作的 logits。这个任务本身并不难但它能清楚展示变分参数探索的训练机制策略参数不是一组固定权重而是一个高斯分布每次训练从分布中采样多组权重分别评估可验证奖励再根据相对优势更新分布。理解了这个最小示例之后再迁移到 LLM LoRA 的场景只需要替换策略前向计算和可验证奖励两部分。5.2 参数化后验与采样我们先实现参数化后验的采样与对数概率计算。这里假设策略参数 θ 服从各向同性的高斯分布即每个参数独立服从 N(μ_i, σ_i²)。为了数值稳定性网络保存的是 log σ² 而不是 σ。import torch import torch.nn.functional as F from torch.distributions import Categorical # 文件路径param_exploration_rlvr.py # 运行环境Python 3.9PyTorch 2.x # ---------- 1. 可验证奖励 ---------- def verifiable_reward(actions, labels): 规则化验证状态等于动作得 1 分否则 0 分。 return (actions labels).float() # ---------- 2. 参数化后验 q(theta; mu, log_sigma) ---------- def sample_params(mu, log_sigma, k): 通过重参数化从高斯分布中采样 k 组参数。 返回 tensor 形状为 [k, D]。 sigma torch.exp(log_sigma) eps torch.randn(k, *mu.shape) theta mu.unsqueeze(0) eps * sigma.unsqueeze(0) return theta def log_prob_q(theta, mu, log_sigma): 计算每组采样参数在 q 下的对数概率返回 [k]。 sigma torch.exp(log_sigma) log_p ( -0.5 * ((theta - mu.unsqueeze(0)) / sigma.unsqueeze(0)) ** 2 - log_sigma.unsqueeze(0) - 0.5 * torch.log(torch.tensor(2 * torch.pi)) ) return log_p.sum(dim-1) def kl_prior(mu, log_sigma, prior_std1.0): 高斯分布与标准正态先验之间的 KL 散度。 sigma torch.exp(log_sigma) kl ( torch.log(torch.tensor(prior_std)) - log_sigma (sigma ** 2 mu ** 2) / (2 * prior_std ** 2) - 0.5 ) return kl.sum()这段代码里sample_params用的是标准重参数化技巧eps是标准正态采样theta mu eps * sigma。因为mu和log_sigma设置了requires_gradTrue所以theta对它们可导。log_prob_q则是 score function 更新时需要的log q(θ_k; φ)它把每组采样参数映射成一个标量对数概率。5.3 训练循环与损失接下来是核心训练循环。我们维护mu和log_sigma两个可学习变量每次迭代采样K组参数向量化计算所有参数在全部状态下的 logits采样动作计算可验证奖励和组内优势最后用 score function 加 KL 正则更新分布。torch.manual_seed(42) # 线性策略: 2 输入 - 2 输出共 2*2 2 6 个参数 param_dim 2 * 2 2 mu torch.zeros(param_dim, requires_gradTrue) log_sigma torch.zeros(param_dim, requires_gradTrue) # sigma 初始为 1 states torch.eye(2) # one-hot 状态集: [[1,0],[0,1]] labels torch.tensor([0, 1]) K 32 beta 0.01 optimizer torch.optim.Adam([mu, log_sigma], lr0.02) def evaluate_accuracy(mu_value): 用均值参数评估当前策略在全部状态上的正确率。 W mu_value[:4].view(2, 2) b mu_value[4:].unsqueeze(0) logits torch.einsum(si,io-so, states, W) b actions logits.argmax(dim-1) return (actions labels).float().mean().item() for step in range(500): optimizer.zero_grad() # 1. 采样 K 组策略参数 theta sample_params(mu, log_sigma, K) # [K, 6] # 2. 向量化前向传播 W theta[:, :4].view(K, 2, 2) # [K, 2, 2] b theta[:, 4:].unsqueeze(1) # [K, 1, 2] logits torch.einsum(si,kio-kso, states, W) b # [K, 2, 2] # 3. 采样动作并计算可验证奖励 probs F.softmax(logits, dim-1) dist Categorical(probs) actions dist.sample() # [K, 2] rewards verifiable_reward(actions, labels.unsqueeze(0)) # [K, 2] rewards rewards.mean(dim-1) # 每个采样参数的平均奖励 [K] # 4. 组内基线 优势 baseline rewards.mean() advantage rewards - baseline # [K] # 5. score function 估计 KL 正则 loss -(advantage * log_prob_q(theta, mu, log_sigma)).mean() loss loss beta * kl_prior(mu, log_sigma) loss.backward() optimizer.step() if (step 1) % 50 0: with torch.no_grad(): mu_value mu.detach() sigma_value torch.exp(log_sigma.detach()) acc evaluate_accuracy(mu_value) print(fstep {step 1}: loss{loss.item():.4f}, fmu_acc{acc:.2f}, sigma_mean{sigma_value.mean().item():.4f})代码的关键点在于actions是从 softmax 分布中采样出来的它本身不可导但rewards只是参与构造一个常数权重advantage真正的梯度来自log_prob_q(theta, mu, log_sigma)。这正好对应第 4 节推导的 score function 更新公式。kl_prior则保证参数分布不会过快坍缩训练后期即便mu已经收敛到正确方向log_sigma也能保持一个合理的探索宽度。5.4 运行结果与含义运行上面的代码预期会看到类似下面的输出step 50: loss-0.0432, mu_acc0.50, sigma_mean0.9821 step 100: loss-0.1186, mu_acc0.50, sigma_mean0.9013 step 150: loss-0.1652, mu_acc0.85, sigma_mean0.7234 step 200: loss-0.2110, mu_acc1.00, sigma_mean0.6512 step 250: loss-0.2087, mu_acc1.00, sigma_mean0.5810 ...由于代码中涉及随机采样不同机器和随机种子下的输出不会完全一致但整体趋势应该是mu_acc逐步逼近 1.0sigma_mean逐渐下降但不会变成 0。mu_acc表示用参数分布的均值作为最终策略时的验证正确率它从随机水平0.5涨到接近满分的过程说明参数分布整体在向高奖励区域移动。sigma_mean下降则说明分布越来越集中探索范围逐步收缩这是置信度提高的自然表现。如果你把beta调得很大比如 1.0会看到sigma_mean很快下降但mu更新变慢正确率上升变缓如果把beta调得太小比如 1e-5有可能出现sigma_mean变为 0 的情况也就是分布完全坍缩模型失去探索能力。这个实验非常直观地展示了参数探索中探索与利用的平衡。6. 常见问题与排查思路在实际复现和移植这个示例时下面几个问题比较常见可以按表格快速定位。问题现象常见原因解决思路训练初期 loss 剧烈震荡K 太小score function 方差过大增大采样组数 K或增加组内 baseline 的稳定性sigma 迅速坍缩到接近 0KL 正则系数 β 太小分布过早变确定性调大 β或在 log_sigma 更新时加权重剪裁mu 一直停在原地acc 不涨采样参数几乎全是负优势有效更新方向太弱增大初始化 sigma或提高学习率并调大 K奖励始终为 0探索范围不够采样策略完全覆盖不到正确答案增大初始 log_sigma或结合动作层面熵正则验证集表现远低于训练集参数分布在训练后期过度拟合验证器增加验证器多样性或冻结分布方差并做早停大模型场景下显存不足为全部权重维护均值和方差导致参数翻倍只对 LoRA 参数或低秩子空间做参数探索关于第一个问题score function 估计的方差和采样参数维度、奖励尺度都有关系。最简单有效的缓解手段始终是两个增大 K以及使用更稳定的基线。组内 baseline 比全局 baseline 好很多如果问题的奖励尺度跨度过大还可以对每个问题单独做归一化也就是把组内奖励除以组内标准差。第二个问题在变分方法里特别典型。log_sigma的梯度在奖励较高时倾向于让分布更集中如果 KL 正则太弱几步之内方差就可能崩掉。除了调大 β还有一个工程技巧在 log_sigma 更新后做 clamp例如限制在 [-4, 0] 范围内保证 sigma 最小不小于约 0.018。这会损失一部分理论上的优雅性但工程上很稳定。7. 工程实践与扩展讨论7.1 大规模模型怎么做参数探索直接把示例里的思路套到几十亿参数的大模型上会遇到两个问题显存和计算开销、梯度方差。最可行的方案是把参数探索限制在 LoRA 适配器参数上。基础模型权重保持冻结只有 LoRA 参数被建模成高斯分布。LoRA 参数数量通常只有基础模型的千分之一到百分之一为每个 LoRA 参数维护均值和方差完全可承受。具体做法是先准备一组 LoRA 权重作为 μ 的初始值然后为 log σ² 初始化一个较小的值比如 -4 或 -3。每次训练从分布中采样 K 组 LoRA 权重分别做前向生成用规则验证器打分再按 score function 更新 LoRA 的均值和方差。这样既保留了基础模型的通用能力又让小规模的适配器参数承担探索功能算是一个性价比很高的折中方案。7.2 调参与稳定性建议从工程角度看变分参数探索的稳定性主要受三个超参数影响采样组数 K、KL 正则系数 β、以及 log σ² 的初始化。K 控制梯度的方差通常不要小于 16推荐 32 到 64。K 越大组内基线越稳定但生成和验证的开销也线性增长。β 控制探索强度建议先用 0.01 到 0.1 做网格搜索。观察训练曲线的 sigma_mean如果它下降过快就调大 β如果 acc 不涨就调小 β。log σ² 的初始化决定了探索的初始范围。初始过大容易让前几步的采样策略完全随机初始过小又会错过正确答案区域。建议从 -2 到 0 之间尝试。另外建议训练过程中持续记录三组指标mu 对应策略在验证集上的准确率、sigma_mean 的均值、以及组内标准差的分布。这三个指标能帮你快速判断训练是处于探索不足还是过度探索状态比单看 loss 直观得多。7.3 与现有 RLVR 流程的结合点如果你的团队已经在用 GRPO 或类似流程做 RLVR那么引入变分参数探索并不需要推翻现有架构只需要把固定权重前向改成从参数分布采样权重前向。生成的 K 组响应原本来自同一个策略权重现在可以来自 K 个不同的采样权重可验证奖励和组内 baseline 的计算方式不变更新时除了常规的策略梯度再补上对参数分布均值和方差的更新即可。更激进的方案是把动作探索和参数探索叠加使用。比如在采样参数权重的同时仍然在 token 采样时使用较高的温度或熵正则。动作探索负责在给定行为模式内的精细搜索参数探索负责在行为模式间的跳跃式搜索两者互补。这种组合在奖励特别稀疏的数学推理和代码生成任务上往往比单用其中一种效果更稳。8. 总结与下一步学习方向本文从一个 RLVR 训练中的实际痛点出发梳理了动作探索与参数探索的区别解释了变分学习的核心原理并给出了一套最小可运行的 PyTorch 示例。读完你应该掌握几个关键点RLVR 的奖励来自规则验证器而不是奖励模型这决定了梯度无法直接依赖奖励的可微性变分参数探索把策略参数建模成分布通过重参数化采样和 score function 更新来驱动探索组内 baseline 是稳定 RLVR 训练的重要组件它和 GRPO 的优势思想天然契合。下一步可以从两个方向继续深入一是把本文的线性策略替换成 LoRA LLM 生成在一个开源数学数据集上跑完整的 RLVR 实验二是研究更高级的变分策略方法比如通过重要性采样和 clip 机制控制参数更新的步长避免方差过大破坏已经学到的能力。调参时优先观察 sigma_mean 和验证集准确率这两条曲线它们比 loss 更能反映参数探索的健康程度。如果你准备在自己的项目里复现这套方案建议先用本文代码跑通训练循环再逐步替换成真实任务和验证器。