梯度裁剪需在loss.backward()后、optimizer.step()前调用clip_grad_norm_,它原地修改梯度、支持none梯度、返回裁剪前范数;clip_grad_value_适用于梯度分布均匀的简单模型,二者不可混用。

PyTorch 中梯度裁剪不是模型定义的一部分,而是在 optimizer.step() 之前对 model.parameters() 的梯度做原地修改 —— 忘记这一步,裁剪就完全失效。
为什么 torch.nn.utils.clip_grad_norm_ 是首选
它直接操作参数的 grad 属性,支持按范数(L2)或值(L∞)裁剪,且能处理含 None 梯度的参数(比如被 requires_grad=False 冻结的层),不会报错。比手动遍历 clip_grad_value_ 更鲁棒,尤其适合带 embedding 或 RNN 的模型。
常见错误现象:RuntimeError: grad is None —— 这往往是因为调用位置错了,或某些参数没参与当前 backward。
- 必须在
loss.backward()之后、optimizer.step()之前调用 - 推荐传入整个模型参数列表:
model.parameters(),而非单层参数,避免漏裁 - 阈值
max_norm通常设为 1.0(NLP 常见)、5.0(CV 稍宽松),太小会导致训练停滞,太大等于没裁 - 返回值是裁剪前的总梯度范数,可用于调试:
total_norm = torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
clip_grad_value_ 适合什么场景
它对每个参数的梯度单独截断到 [-clip_value, clip_value] 区间,不考虑参数规模差异,因此更适用于梯度分布较均匀、或你明确知道异常梯度只出现在特定范围的简单模型(如小型 MLP)。不适合 embedding 层或不同尺度参数混用的结构。
- 调用方式类似:
torch.nn.utils.clip_grad_value_(model.parameters(), clip_value=1e-2) - 如果某个参数梯度全为
None,它会跳过,不报错 - 注意:该函数没有返回值,无法感知是否真发生了裁剪
- 和
clip_grad_norm_不能混用,否则后者可能被前者覆盖,导致裁剪逻辑混乱
容易被忽略的两个细节
一是分布式训练(DDP)下,梯度在 backward() 后已自动同步,clip_grad_norm_ 只需在主进程调用一次即可,无需加 if rank == 0 判断;二是使用混合精度(torch.cuda.amp)时,必须在 scaler.step(optimizer) 前裁剪,且应作用于 scaler.unscale_(optimizer) 之后的梯度 —— 否则裁剪的是缩放前的小数值,等效阈值被放大了 scaler.get_scale() 倍。
- AMP 正确顺序:
loss.backward()→scaler.unscale_(optimizer)→clip_grad_norm_(...)→scaler.step(optimizer) - 若用
torch.compile,裁剪仍需放在 Python 控制流中,不能被编译进图里
梯度裁剪本身不改变模型结构或前向逻辑,但它依赖你对 backward 调用时机和 optimizer 生命周期的准确把握;一旦插入位置错位,就只剩心理安慰作用。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











