ms-swift 中 SAPO 实践:用温度控制的软门控替代 GRPO 硬裁剪
【免费下载链接】swiftUse PEFT or Full-parameter to CPT/SFT/DPO/GRPO 600+ LLMs (Qwen3.6, DeepSeek-V4, GLM-5.1, InternLM3, Llama4, ...) and 300+ MLLMs (Qwen3-VL, Qwen3-Omni, InternVL3.5, Ovis2.5, GLM4.5v, Gemma4, Llava, Phi4, ...) (AAAI 2025).项目地址: https://gitcode.com/GitHub_Trending/swift1/swift
本文基于 ms-swift 仓库的官方文档 SAPO 与对应源码实现,系统讲解 Soft Adaptive Policy Optimization(SAPO)算法的原理、在 ms-swift 中的落地方式与完整训练配置。读完本文,你将理解 GRPO 硬裁剪在离策略(off-policy)训练下的困境、SAPO 温度软门控的数学形式,并能够直接复制仓库中的训练脚本,用--loss_type sapo在 swift 与 Megatron 两条训练链路中启用该算法。
背景:GRPO 硬裁剪的两难困境
GRPO(Group Relative Policy Optimization)通过组内相对优势估计来训练大语言模型,在处理离策略数据时依赖 token 级重要性采样比率:
$$ r_t = \frac{\pi_\theta(y_t|x, y_{<t})}{\pi_{\theta_{\mathrm{old}}}(y_t|x, y_{<t})} $$
问题在于,token 级重要性采样比率往往具有高方差,且在以下场景中会被进一步放大:
- 长文本生成:序列越长,逐 token 比率的累积偏差越大;
- MoE 模型路由异构性:采样阶段使用的旧策略模型与训练模型可能命中不同的专家路由,导致同一 token 的 log-probability 差异被显著放大。
为限制策略更新幅度,GRPO 采用硬裁剪:
$$ L^{\mathrm{GRPO}} = -\min\left( r_t \cdot A, \mathrm{clip}(r_t, 1-\epsilon, 1+\epsilon) \cdot A \right) $$
硬裁剪的两难:裁剪窗口太窄(ε 小),大量有效样本的梯度被直接截断,学习信号损失严重;裁剪窗口太宽(ε 大),离策略样本携带的高方差噪声梯度涌入训练,造成训练不稳定。ms-swift 通过提供多种loss_type(grpo/dapo/cispo/sapo等)让训练者按场景选择策略约束方式,SAPO 是其中用"软衰减"替代"硬截断"的选项。
SAPO 方法:温度控制的 Sigmoid 软门控
SAPO(Soft Adaptive Policy Optimization,论文 arXiv:2511.20347)的核心思想是:不再对重要性采样比率做截断,而是用一个温度控制的 sigmoid 软门控函数对比率进行平滑缩放,使离策略更新的梯度连续、平滑地衰减,同时保留有用的学习信号。
软门控函数
SAPO 的正负优势分别使用不同温度的门控。
正优势($A > 0$)时使用正向门控:
$$ g^{+}t = \sigma\left( \tau{\mathrm{pos}} \cdot (r_t - 1) \right) \cdot \frac{4}{\tau_{\mathrm{pos}}} $$
负优势($A < 0$)时使用负向门控:
$$ g^{-}t = \sigma\left( \tau{\mathrm{neg}} \cdot (r_t - 1) \right) \cdot \frac{4}{\tau_{\mathrm{neg}}} $$
其中:
- $\sigma(\cdot)$ 为 sigmoid 函数;
- $\tau_{\mathrm{pos}}$ 与 $\tau_{\mathrm{neg}}$ 为温度参数,控制门控函数的斜率(衰减速度);
- $r_t$ 为 token 级重要性采样比率。
可以直观理解为:当 $r_t \approx 1$(在策略)时,门控输出约为 $2/\tau$,接近"满额"学习信号;当 $r_t$ 明显偏离 1(离策略)时,sigmoid 将梯度按平滑曲线衰减,而不是像硬裁剪那样直接置零。
SAPO 损失函数
$$ L^{\mathrm{SAPO}} = -g_t \cdot A $$
其中 $A > 0$ 时取 $g_t = g^{+}_t$,$A < 0$ 时取 $g_t = g^{-}_t$。注意该损失本身不含重要性采样比率的乘项——对策略强度的约束完全由软门控 $g_t$ 承担。
温度参数的作用与取值建议
温度参数 $\tau$ 控制软门控的衰减速率:$\tau$ 越大,门控越锐利,越接近硬裁剪的行为;$\tau$ 越小,门控越平滑。
论文给出了一条不对称的调参建议:正优势会提升被采样 token 的 logit、同时压低其余所有未采样 token 的 logit;而负优势则相反——它一次性抬高大量未采样 token 的 logit,可能把梯度扩散到许多无关 token 上,引入不稳定。因此论文推荐 $\tau_{\mathrm{neg}} > \tau_{\mathrm{pos}}$,让负奖励 token 的梯度衰减得更快。默认推荐值为 $\tau_{\mathrm{pos}} = 1.0$、$\tau_{\mathrm{neg}} = 1.05$。
ms-swift 中的实现细节
损失计算主路径
在 GRPO 训练器的损失函数中,sapo与grpo/dapo/cispo等并列为一等公民分支。从源码 grpo_trainer.py#L1044-L1050 可以看到核心实现:
elif self.loss_type == 'sapo': gate_pos = torch.sigmoid(self.tau_pos * (coef_1 - 1)) * (4.0 / self.tau_pos) gate_neg = torch.sigmoid(self.tau_neg * (coef_1 - 1)) * (4.0 / self.tau_neg) is_positive = advantages > 0 soft_gate = torch.where(is_positive, gate_pos, gate_neg) per_token_loss = -soft_gate * advantages几点实现层面的观察:
- 与数学定义严格一致:
coef_1即 $r_t = \exp(\text{log_ratio})$,其中log_ratio = per_token_logps - old_per_token_logps(见 grpo_trainer.py#L1013)。代码先同时算出正/负两套门控,再用torch.where按 token 优势符号选择,与公式中 $g_t$ 的分支定义一一对应。 4/τ归一化因子:代码显式实现了公式中的 $4/\tau$ 缩放,使得 $r_t = 1$ 处门控值约为 $2/\tau$。调小 τ 不仅让曲线更平缓,也会整体放大门控增益,两者需结合观察。- ε 裁剪参数在 SAPO 下不参与计算:
epsilon/epsilon_high只在grpo/dapo等分支中用于torch.clamp;sapo分支中策略约束完全由软门控完成。 - KL 正则仍然生效:当
--beta非零时,per-token KL 惩罚会叠加到每个loss_type(包括 sapo)的 token 损失上(见 grpo_trainer.py#L1068-L1069),示例脚本中均使用--beta 0.001。 - 损失归一化方式与 GRPO 一致:sapo 与 grpo 一样,先对每条序列内的有效 token 求均值,再对 batch 求均值(见 grpo_trainer.py#L1087-L1089)。
参数定义与 Liger 内核支持
tau_pos/tau_neg定义在 RLHF 参数混入类中,默认值即论文推荐值(见 args_mixin.py#L430-L431):
# SAPO # Temperature parameters for soft adaptive gate tau_pos: float = 1.0 tau_neg: float = 1.05参数说明(摘自 args_mixin.py#L331-L336):
tau_pos:SAPO 中正向优势的温度参数,控制软门控函数的锐度。较大值使门控更锐利(接近硬裁剪),较小值使门控更平滑。默认 1.0。tau_neg:SAPO 中负向优势的温度参数。通常设置tau_neg > tau_pos以对负优势施加更强约束。默认 1.05。
训练器初始化时把两者缓存到实例属性(见 grpo_trainer.py#L2124-L2125)。此外,ms-swift 还支持通过 Liger 融合内核加速 SAPO:开启--use_liger_kernel时,tau_pos/tau_neg会以sapo_temperature_pos/sapo_temperature_neg参数传入LigerFusedLinearGRPOLoss(见 grpo_trainer.py#L2052-L2068),使软门控在显存友好的分块融合路径中生效。
Megatron-SWIFT 链路同样支持
Megatron 后端训练器实现了与 HF 路径语义一致的 sapo 分支(见 grpo_trainer.py#L1052-L1057),参数默认值同样定义在 megatron_args.py#L84-L85。因此无论使用swift rlhf还是megatron rlhf入口,SAPO 的算法行为与调参方式完全一致。
参数配置与命令行用法
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
--loss_type | str | - | 设置为sapo |
--tau_pos | float | 1.0 | 正向优势温度参数,控制门控斜率 |
--tau_neg | float | 1.05 | 负向优势温度参数,控制门控斜率 |
最小启用示例:
swift rlhf \ --rlhf_type grpo \ --loss_type sapo \ --tau_pos 1.0 \ --tau_neg 1.05 \ # ... 其他参数完整训练脚本(swift 后端)
仓库提供了针对多模态模型(Qwen2.5-VL)配 vLLM colocate 推理的完整脚本 examples/train/grpo/internal/sapo.sh:
# SAPO https://arxiv.org/abs/2511.20347 CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \ NPROC_PER_NODE=8 \ MAX_PIXELS=602112 \ swift rlhf \ --rlhf_type grpo \ --loss_type sapo \ --tau_pos 1 \ --tau_neg 1.05 \ --model Qwen/Qwen2.5-VL-3B-Instruct \ --external_plugins examples/train/grpo/plugin/plugin.py \ --reward_funcs external_r1v_acc format \ --learning_rate 1e-6 \ --use_vllm true \ --vllm_mode colocate \ --vllm_gpu_memory_utilization 0.6 \ --vllm_tensor_parallel_size 1 \ --vllm_max_model_len 8192 \ --tuner_type full \ --torch_dtype bfloat16 \ --dataset AI-ModelScope/clevr_cogen_a_train \ --overlong_filter false \ --importance_sampling_level token \ --max_length 4096 \ --max_completion_length 4096 \ --num_train_epochs 1 \ --per_device_train_batch_size 4 \ --gradient_accumulation_steps 4 \ --num_generations 8 \ --steps_per_generation 32 \ --save_steps 1000 \ --sleep_level 1 \ --offload_model true \ --offload_optimizer true \ --logging_steps 1 \ --dataloader_num_workers 4 \ --temperature 1.0 \ --system 'examples/train/grpo/prompt.txt' \ --deepspeed zero1 \ --log_completions true \ --report_to tensorboard swanlab \ --beta 0.001 \ --attn_impl flash_attention_2脚本要点:全参微调(--tuner_type full)、vLLM colocate 模式加速 rollout、--steps_per_generation 32让同一批 prompt 的生成样本被复用多个优化步——这正是产生显著 off-policy 程度、SAPO 软门控价值最大的典型场景;--beta 0.001提供轻量 KL 正则。
完整训练脚本(Megatron 后端)
Megatron 链路对应脚本 examples/megatron/grpo/sapo.sh,采用相同的模型、数据集与奖励函数,差异主要在并行与批参数:
# SAPO https://arxiv.org/abs/2511.20347 CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \ NPROC_PER_NODE=8 \ MAX_PIXELS=602112 \ megatron rlhf \ --rlhf_type grpo \ --loss_type sapo \ --tau_pos 1 \ --tau_neg 1.05 \ --model Qwen/Qwen2.5-VL-3B-Instruct \ --context_parallel_size 1 \ --tensor_model_parallel_size 1 \ --pipeline_model_parallel_size 1 \ --dataset AI-ModelScope/clevr_cogen_a_train \ --save_safetensors true \ --external_plugins examples/train/grpo/plugin/plugin.py \ --reward_funcs external_r1v_acc format \ --dynamic_sample false \ --steps_per_generation 4 \ --micro_batch_size 2 \ --global_batch_size 128 \ --num_generations 8 \ --use_vllm true \ --vllm_mode colocate \ --vllm_gpu_memory_utilization 0.7 \ --vllm_max_model_len 8192 \ --max_length 4096 \ --max_completion_length 4096 \ --tuner_type full \ --bf16 true \ --importance_sampling_level token \ --epsilon 0.2 \ --epsilon_high 0.2 \ --overlong_filter true \ --num_train_epochs 1 \ --eval_steps 1000 \ --save_steps 1000 \ --sleep_level 2 \ --offload_model true \ --offload_optimizer true \ --logging_steps 1 \ --recompute_granularity selective \ --finetune \ --lr 1e-6 \ --dataloader_num_workers 8 \ --dataset_num_proc 8 \ --no_save_optim \ --no_save_rng \ --attention_backend flash \ --temperature 1.0 \ --system examples/train/grpo/prompt.txt \ --beta 0.001 \ --padding_free true \ --report_to wandb两个脚本中--epsilon/--epsilon_high仅在 Megatron 脚本中显式出现,但如前文源码分析所示,loss_type sapo分支并不使用裁剪参数,它们不影响 SAPO 的软门控行为。
适用边界与注意事项
官方文档对 SAPO 有两个明确的适用前提,使用时务必注意:
- 软门控只在离策略训练时产生实际作用。门控输入是 $r_t - 1$:完全在策略(每次 rollout 后立即更新、$r_t \approx 1$)时,门控近似常数,SAPO 退化为带缩放的优势加权;当
steps_per_generation、多步优化或异步 rollout 带来明显策略漂移时,软门控的平滑衰减才真正替代硬裁剪发挥作用。 - 与 GSPO 的采样粒度互斥。SAPO 的重要性采样粒度是 token 级(即
importance_sampling_level保持默认token),而 GSPO(Group Sequence Policy Optimization)使用 sequence 级比率(--importance_sampling_level sequence),两者设计冲突,不能叠加使用。
从源码结构看,还有两点可验证的工程事实:importance_sampling_level支持token/sequence/sequence_token三个取值(见 args_mixin.py#L327-L330),SAPO 应显式使用token;此外 OPD-RL(teacher 蒸馏)场景下不支持的loss_type白名单中不包含 sapo,但从 grpo_trainer.py#L1035-L1038 的分流逻辑看,sapo 走的是 per-token 优势路径,属于 teacher 模式下可用的损失类型之一。
小结
SAPO 用 $\tau$ 控制的 sigmoid 软门控替换 GRPO 的硬裁剪,在长文本生成与 MoE 路由异构等高方差离策略场景下提供"平滑衰减而非硬截断"的梯度约束,正负优势通过 $\tau_{\mathrm{pos}}$(默认 1.0)与 $\tau_{\mathrm{neg}}$(默认 1.05)非对称调温。在 ms-swift 中,只需将--loss_type设为sapo并按需调整两个温度参数,即可在swift rlhf与megatron rlhf两条链路上使用;参考脚本位于 examples/train/grpo/internal/sapo.sh 与 examples/megatron/grpo/sapo.sh,核心实现见 swift/rlhf_trainers/grpo_trainer.py 与 swift/megatron/trainers/grpo_trainer.py。若训练中出现明显离策略漂移导致的损失尖峰,可优先尝试增大tau_neg让负优势梯度衰减更快,再辅以较小的--betaKL 正则稳定训练。
【免费下载链接】swiftUse PEFT or Full-parameter to CPT/SFT/DPO/GRPO 600+ LLMs (Qwen3.6, DeepSeek-V4, GLM-5.1, InternLM3, Llama4, ...) and 300+ MLLMs (Qwen3-VL, Qwen3-Omni, InternVL3.5, Ovis2.5, GLM4.5v, Gemma4, Llava, Phi4, ...) (AAAI 2025).项目地址: https://gitcode.com/GitHub_Trending/swift1/swift
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考