`deepcopy` of a container of tracked numbers gives zero gradients, with no error

已关闭
#327 2 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
3-5 天
新手友好度
46/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
活跃
技术栈
julia

调研方向

Start by reading src/tracked.jl and inspect how deepcopy and deepcopy_internal handle TrackedReal, TrackedArray, and containers. Reproduce the supplied examples, including the copied-tape case, before choosing an approach. Done means container copies preserve gradients while deep-copying a tape still produces an independent tape.

由索引模型根据 Issue 内容生成。

描述

Thank you for your incredible work. Below is a Claude-generated reduction of an error I ran into when using deepcopy - I hope its useful. Let me know if I can do anything, and my apologies if this is a known error.

-- claude --

deepcopy of anything that holds tracked numbers, such as a Vector, Tuple, NamedTuple or TrackedArray, cuts them off from the reverse pass. The gradient comes back as zeros, with no error or warning. A bare TrackedReal is fine.

using ReverseDiff

x = [3.0, 4.0]

println("No deepcopy:       ", ReverseDiff.gradient(x -> sum([x[1], x[2]]), x))
println("Bare numbers:      ", ReverseDiff.gradient(x -> deepcopy(x[1]) + deepcopy(x[2]), x))
println("Vector:            ", ReverseDiff.gradient(x -> sum(deepcopy([x[1], x[2]])), x))
println("Tuple:             ", ReverseDiff.gradient(x -> sum(deepcopy((x[1], x[2]))), x))
println("NamedTuple:        ", ReverseDiff.gradient(x -> sum(deepcopy((; a = x[1], b = x[2]))), x))
println("TrackedArray:      ", ReverseDiff.gradient(x -> sum(deepcopy(x)), x))
println("Next to original:  ", ReverseDiff.gradient(x -> x[1] + deepcopy((x[1],))[1], x))

Output:

No deepcopy:       [1.0, 1.0]
Bare numbers:      [1.0, 1.0]
Vector:            [0.0, 0.0]
Tuple:             [0.0, 0.0]
NamedTuple:        [0.0, 0.0]
TrackedArray:      [0.0, 0.0]
Next to original:  [1.0, 0.0]

The right answer is [1.0, 1.0] for all but the last, where it is [2.0, 0.0]. A compiled tape gives the same zeros for the Vector.

Where it seems to come from

src/tracked.jl has Base.deepcopy(t::T) where {T <: TrackedReal} = t, so a bare number stays on the tape. Inside a container, Julia calls Base.deepcopy_internal instead of deepcopy, and ReverseDiff has no method for it. The numbers then seem to get their own copy of the tape, which the reverse pass never reaches.

A possible fix, and its catch

With these two methods, all of the cases above are right. With only the first one, all but TrackedArray are right.

Base.deepcopy_internal(t::ReverseDiff.TrackedReal, ::IdDict) = t
Base.deepcopy_internal(t::ReverseDiff.TrackedArray, ::IdDict) = t

But with both of them, deepcopy of a tape no longer gives an independent tape. Running the copy changes the original:

tape = ReverseDiff.GradientTape(x -> sum(abs2, x), [3.0, 4.0])
copied = deepcopy(tape)
ReverseDiff.gradient!(zeros(2), copied, [1.0, 1.0])
ReverseDiff.value(tape.output)  # 25.0 without the methods, 2.0 with them

So the fix probably needs more care than this.

Versions

  • ReverseDiff v1.18.7, the newest version in the General registry on 2026-11-04
  • Julia 1.13.3
  • macOS, arm64 (Apple M5)

The example ran in a clean environment with only ReverseDiff installed. It hasn't been tried on other platforms.

主要语言
Julia
星标
397
派生
62
平均合并
6 小时 46 分钟
30 天内合并 PR
5

环境准备

这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

JuliaDiff/ReverseDiff.jl 的其他 Issue

查看 JuliaDiff/ReverseDiff.jl 的全部 Issue

相似的 Issue

更多 Julia Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。