From 904adcff1f7237d36768715b184acc79b6fec0ab Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Thu, 22 Oct 2026 13:18:03 +0200 Subject: [PATCH 1/3] Test `mean(sin, A)` on positive data Since #662 the Float16 statistics tests draw integers from 1:100, which keeps std and var stable but lets the sines cancel: the mean of four can land near zero, where the GPU's and the CPU's summation order differ by more than isapprox allows. Over random inputs that is rare (about 0.5% on Metal), but the test runner seeds the RNG per file and Metal draws a seed on every kernel launch, so the input depends on how many kernels the preceding tests launched. #825 adds one to `mean(A; dims)`, which makes Metal on Julia 1.10 pick a cancelling input every time. Draw the sin inputs from [0, 1) again, as before #662. --- test/testsuite/statistics.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/testsuite/statistics.jl b/test/testsuite/statistics.jl index fb9edfc28..a4ac0f43c 100644 --- a/test/testsuite/statistics.jl +++ b/test/testsuite/statistics.jl @@ -30,9 +30,11 @@ using Statistics @test compare(mean, AT, rand(range, 2, 2)) @test compare(A -> mean(A; dims = 2), AT, rand(range, 2, 2)) @test compare(A -> mean(A; dims = [1, 3]), AT, rand(range, 2, 2, 2)) - @test compare(A -> mean(sin, A), AT, rand(range, 2, 2)) - @test compare(A -> mean(sin, A; dims = 2), AT, rand(range, 2, 2)) - @test compare(A -> mean(sin, A; dims = [1, 3]), AT, rand(range, 2, 2, 2)) + # sin of `range` can cancel to near zero, where the summation order decides the + # result; values in [0, 1) keep every term positive + @test compare(A -> mean(sin, A), AT, rand(ET, 2, 2)) + @test compare(A -> mean(sin, A; dims = 2), AT, rand(ET, 2, 2)) + @test compare(A -> mean(sin, A; dims = [1, 3]), AT, rand(ET, 2, 2, 2)) end end From f59e80da67d2c21643548cea189201be43b4c8a8 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Thu, 22 Oct 2026 12:58:17 +0200 Subject: [PATCH 2/3] Multiply unit-magnitude ComplexF16 values in the mapreducedim! tests The product of ten ComplexF16 values from [0, 1) is typically around 1e-4. Its squared components underflow, so `norm` of the one-element result is zero and the array `isapprox` accepts no difference at all; products that land in the subnormal range also round differently on the CPU and the GPU. Either made the whole-vector `*` reductions fail in about 0.1% of runs on Metal. Values on the unit circle keep the product near one. --- test/testsuite/reductions.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/testsuite/reductions.jl b/test/testsuite/reductions.jl index de2ced002..dc9ebec0b 100644 --- a/test/testsuite/reductions.jl +++ b/test/testsuite/reductions.jl @@ -25,18 +25,21 @@ test_shapes(ET, all, few) = ET in (Float32, ComplexF32) ? all : few @testsuite "reductions/mapreducedim!" (AT, eltypes)->begin @testset "$ET" for ET in eltypes range = ET <: Real ? (ET(1):ET(10)) : ET + # ten ComplexF16 values from [0, 1) multiply to a magnitude `norm` cannot resolve in + # Float16, so the array comparison tolerates no error; unit values keep it near one + prod_data(sz) = ET == ComplexF16 ? ET.(cis.(2π .* rand(sz...))) : rand(range, sz) for (sz,red) in test_shapes(ET, [(10,)=>(1,), (10,10)=>(1,1), (10,10,10)=>(1,1,1), (10,10,10)=>(10,10,10), (10,10,10)=>(1,10,10), (10,10,10)=>(10,1,10), (10,10,10)=>(10,10,1), (0,)=>(1,)], [(10,)=>(1,), (10,10,10)=>(10,1,10), (0,)=>(1,)]) # mapreducedim! @test compare((A,R)->Base.mapreducedim!(identity, +, R, A), AT, rand(range, sz), zeros(ET, red)) - @test compare((A,R)->Base.mapreducedim!(identity, *, R, A), AT, rand(range, sz), ones(ET, red)) + @test compare((A,R)->Base.mapreducedim!(identity, *, R, A), AT, prod_data(sz), ones(ET, red)) @test compare((A,R)->Base.mapreducedim!(x->x+x, +, R, A), AT, rand(range, sz), zeros(ET, red)) # reducedim! @test compare((A,R)->Base.reducedim!(+, R, A), AT, rand(range, sz), zeros(ET, red)) - @test compare((A,R)->Base.reducedim!(*, R, A), AT, rand(range, sz), ones(ET, red)) + @test compare((A,R)->Base.reducedim!(*, R, A), AT, prod_data(sz), ones(ET, red)) end # implicit singleton dimensions From 0680d0593f3fed86ab3a43270307f935005f90f8 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Thu, 22 Oct 2026 12:58:24 +0200 Subject: [PATCH 3/3] Keep the Float16 covariances finite #662 drew the Float16 cov and cor inputs from 1:100, which avoids the subnormal variances of two-element rows in `cor(A; dims=2)` but makes `cov` of 100 samples overflow: the diagonal is Inf on both sides, so the array `isapprox` compares element by element, and the near-zero off-diagonal then differs with the summation order (about 1.7% of runs on Metal). Integers from 1:10 avoid both problems. --- test/testsuite/statistics.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/testsuite/statistics.jl b/test/testsuite/statistics.jl index a4ac0f43c..29886163c 100644 --- a/test/testsuite/statistics.jl +++ b/test/testsuite/statistics.jl @@ -43,8 +43,9 @@ using Statistics if !(ET in [Float32, Float64, Float16, ComplexF64]) continue end - # Larger range for Float16 to avoid numerical instability - range = ET == Float16 ? (ET(1):ET(100)) : ET + # Integers for Float16, so that `cor` of two-element rows have no subnormal variances, + # but small ones, so that `cov` of 100 samples does not overflow + range = ET == Float16 ? (ET(1):ET(10)) : ET @testset "cov" begin s = 100