原帖在自定义层循环里多次给 u_i[:,j,:] 和 alpha_i[:,j,:] 赋值,反向时报:
RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation
clone 一次为什么还不够
u_i[:,j,:] 是视图,后面写入同一块存储会增加版本计数。反向可能需要此前保存的值,即使外层循环 clone 了整个 u_i,内层仍可能在一次图中反复覆盖它。应根据异常检测返回的前向行找出“哪个值被保存、之后又在哪被改写”。
切片赋值也是原地修改。即使循环开始时 clone 过整个张量,后续共享该存储的视图如果被反向保存,再次写入仍可能冲突。
在现代 PyTorch 上,把异常检测包住前向与反向,而不是只包 backward。inputs、targets 和 criterion 是自己的当前批次:
import torch
print("josh050608")
print("input:", inputs.shape, inputs.dtype, inputs.device)
with torch.autograd.detect_anomaly():
outputs = model(inputs)
loss = criterion(outputs, targets)
loss.backward()
异常检测应以函数调用或上下文方式启用,不要把 torch.autograd.set_detect_anomaly 这个函数直接赋值成 True。根据返回的前向位置查找切片赋值、+=、mul_() 和 inplace=True。原题首先应检查两次 u_i[:, j, :] = ... 以及随后的 alpha 切片回写。
一种改法是把每个人的新状态先作为独立张量保存,再 stack,不写回仍参与求导的旧状态。以下是结构示例,old_u 是本轮开始的状态,updates 和 mask 是此前算好的同形状更新与掩码:
parts = []
for j in range(old_u.shape[1]):
value = (1 - lam) * old_u[:, j, :] + lam * updates[:, j, :]
parts.append(value * mask[:, j, :])
u_i = torch.stack(parts, dim=1)
要先确认原算法是否需要顺序更新;若后一个人依赖前一个人的新状态,应保留该依赖,不能机械改成并行计算。不要用 .data 或 detach 隐藏错误,它们可能直接切断所需梯度。
原帖是很早的 PyTorch 版本;异常检测示例面向现代版本。修复后运行完整前向与反向,确认参数梯度存在,再以小输入比较改写前后的前向值,避免改变算法。
先对照前向数值,再检查梯度
下面只演示“每个人的更新互相独立”的情形,用列表收集结果后 stack。真实层若有顺序依赖,要按原算法保留,不能照搬这个并行假设:
import torch
torch.manual_seed(17)
old_u = torch.randn(2, 3, 4, dtype=torch.float64, requires_grad=True)
updates = torch.randn(2, 3, 4, dtype=torch.float64, requires_grad=True)
mask = torch.tensor([[[1.], [1.], [0.]], [[1.], [0.], [0.]]], dtype=torch.float64)
lam = 0.3
parts = []
for j in range(old_u.shape[1]):
value = (1-lam) * old_u[:, j, :] + lam * updates[:, j, :]
parts.append(value * mask[:, j, :])
new_u = torch.stack(parts, dim=1)
reference = ((1-lam) * old_u + lam * updates) * mask
assert torch.allclose(new_u, reference)
new_u.square().sum().backward()
assert old_u.grad is not None and updates.grad is not None
assert torch.isfinite(updates.grad).all()
print("shape/gradient:", new_u.shape, updates.grad.norm().item())
这个例子把旧状态当作只读输入,更新结果单独构造,因此不需要在反向保存的旧状态上切片回写。原题里的 alpha 也应遵循同样思路:每个 j 的值先独立算好,最后按维度组合;检查是否有一处 alpha 在循环内重新初始化而覆盖此前结果。
不要用 detach 或 .data 来“禁止检查”。它可能让 backward 不再报错,同时让原本需要训练的分支失去梯度。对于明确无需求导的日志或掩码,脱离计算图可以合理;对模型状态更新则应先确认数学依赖。
在小 batch、少量人物上比较改写前后的前向数值,再检查参与更新的 Linear 参数 grad 非空、有限。完成这些后再去掉异常检测,以免调试开销长期留在训练流程中。
参考:原始求助帖;技术依据:PyTorch 自动求导(2.5)、torch.stack(2.5)。