You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
rand(1:n, k) inside a Zygote gradient errors since 1.74.3 #854
Since 1.74.3 (#604), sampling from a range inside a Zygote gradient errors:
using Zygote # ChainRules 1.74.3, Julia 1.13.2
Zygote.gradient(w ->sum(w .*rand(1:4, 5)), ones(5))
# ERROR: Mutating arrays is not supported -- called setindex!(Vector{Int64}, ...)
It works on 1.74.2. rand(1:4, 5) lowers to rand!(rng, ::Vector{Int}, ::UnitRange), which @non_differentiable rand!(::Any, ::Any, ::Any) used to cover. #604 narrowed that rule to rand!(::AbstractRNG, ::AbstractArray, ::Type), so Zygote now differentiates through rand! down to setindex!. rand(rng, 1:4, 5) and rand!(A, 1:4) fail the same way. Found through a Flux test, gradient(m -> logitcrossentropy(m(x), rand(1:4, 5)), m).
fixes all three locally, and keeps Distributions' rand!(rng, s::Sampleable, A) from #603 differentiable. A third argument of ::Any would also cover sampling from other collections, but would again silently drop the gradient when sampling from an array of differentiable values.
The safest thing to do is to revert #604, and then to add a proper rule for 3-arg rand!, covering at least abstractarray 3rd argument and possibly generic collections. cc @devmotion
Hmmm that's unfortunate. I made sure that all Zygote tests pass before merging #604. The previous situation with the too wide non-differentiable rules was worse though, IMO errors are much better than silently incorrect derivatives. It even seems that adding a non-differentiable rule for range sampling would also lead to silently incorrect derivatives as soon as you take derivatives with respect to a parameter of the range?
Here are some concrete examples of why I think we shouldn't revert #604, nor add @non_differentiable rand!(::AbstractRNG, ::AbstractArray, ::AbstractRange).
Sampling from a collection just indexes it at random indices. The indices depend only on the RNG state and the length of the collection, not on its values. So for a fixed seed, the derivative of the samples with respect to the collection's values is well defined and generally nonzero. The rule in ChainRules ≤ 1.74.2 (@non_differentiable rand!(::Any, ::Any, ::Any)) returned nothing for these cases without any error:
In the first two examples every sample is x or x + 1, so the correct gradient is 3 for any seed. The third is the example from #604 (comment); its correct value comes from central finite differences with the same seed. On 1.74.3 all three throw Mutating arrays is not supported, which is the better outcome.
The proposed @non_differentiable rand!(::AbstractRNG, ::AbstractArray, ::AbstractRange) fixes rand(1:4, 5), but on 1.74.3 it also brings back the silently wrong result for the second example: (nothing,) instead of (3.0,). LinRange, StepRangeLen etc. have differentiable values.
There's also a remaining rule that is still too wide on 1.74.3. Scalar sampling goes rand(rng, X) → rand(rng, Random.Sampler(rng, X, Val(1))), which hits @non_differentiable rand(::AbstractRNG, ::Random.Sampler) for every collection:
Restrict rand(::AbstractRNG, ::Random.Sampler) to the samplers that don't hold any differentiable data (SamplerType, SamplerTrivial{<:Random.FloatInterval}, integer range samplers, ...), so that the examples above error instead of returning nothing.
Then add proper rules for sampling from arrays, ranges and tuples, rand(rng, X) and rand(rng, X, dims). The pullback scatters the cotangent back to the sampled indices, i.e. it reuses the getindex pullback. This fixes rand(1:4, 5) from this issue (and the example in the OP) without silently dropping gradients for LinRange & co.
Activity
CarloLucibello commentedon Oct 24, 2026
The safest thing to do is to revert #604, and then to add a proper rule for 3-arg
rand!, covering at least abstractarray 3rd argument and possibly generic collections. cc @devmotiondevmotion commentedon Oct 24, 2026
Hmmm that's unfortunate. I made sure that all Zygote tests pass before merging #604. The previous situation with the too wide non-differentiable rules was worse though, IMO errors are much better than silently incorrect derivatives. It even seems that adding a non-differentiable rule for range sampling would also lead to silently incorrect derivatives as soon as you take derivatives with respect to a parameter of the range?
devmotion commentedon Oct 24, 2026
Here are some concrete examples of why I think we shouldn't revert #604, nor add
@non_differentiable rand!(::AbstractRNG, ::AbstractArray, ::AbstractRange).Sampling from a collection just indexes it at random indices. The indices depend only on the RNG state and the length of the collection, not on its values. So for a fixed seed, the derivative of the samples with respect to the collection's values is well defined and generally nonzero. The rule in ChainRules ≤ 1.74.2 (
@non_differentiable rand!(::Any, ::Any, ::Any)) returnednothingfor these cases without any error:In the first two examples every sample is
xorx + 1, so the correct gradient is 3 for any seed. The third is the example from #604 (comment); its correct value comes from central finite differences with the same seed. On 1.74.3 all three throwMutating arrays is not supported, which is the better outcome.The proposed
@non_differentiable rand!(::AbstractRNG, ::AbstractArray, ::AbstractRange)fixesrand(1:4, 5), but on 1.74.3 it also brings back the silently wrong result for the second example:(nothing,)instead of(3.0,).LinRange,StepRangeLenetc. have differentiable values.There's also a remaining rule that is still too wide on 1.74.3. Scalar sampling goes
rand(rng, X)→rand(rng, Random.Sampler(rng, X, Val(1))), which hits@non_differentiable rand(::AbstractRNG, ::Random.Sampler)for every collection:My proposal:
rand(::AbstractRNG, ::Random.Sampler)to the samplers that don't hold any differentiable data (SamplerType,SamplerTrivial{<:Random.FloatInterval}, integer range samplers, ...), so that the examples above error instead of returningnothing.rand(rng, X)andrand(rng, X, dims). The pullback scatters the cotangent back to the sampled indices, i.e. it reuses thegetindexpullback. This fixesrand(1:4, 5)from this issue (and the example in the OP) without silently dropping gradients forLinRange& co.