LinearAlgebra.mul! with Matrix{Dual} does not dispatch to BLAS
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 52/100
- Tipo di issue
- Bug
- Chiarezza
- Abbastanza chiara
- Stato di attività
- Attiva
- Stack tecnologico
- julia
- Ambito
- performance
Direzione di ricerca
Start by running the provided Julia reproducer and inspecting the 3- and 5-argument LinearAlgebra.mul! paths for Matrix{Dual}. The work is done when GEMM cases involving Dual matrices use BLAS-backed operations where applicable and retain the correctness shown by the final comparison.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
I have noticed that GEMM of matrices involving Dual numbers are not dispatched to BLAS, which significantly impacts performance. This happens with 3- and 5-arguments versions of LinearAlgebra.mul!. Generally, any GEMM of matrices resulting in a Matrix{<:Dual} is impacted, e.g. mul!(C, A, B) will be slow if A isa Matrix{<:Dual} and B isa Matrix{Float64}.
This is illustrated in the reproducer below for the 3-argument method. To speed things up, matrices are split into values and partials, and all the cross terms explicitly calculated. These are dispatched to BLAS because values and partials are Matrix{<:BlasFloat}. On my machine, I get ~30x speedup for this example. The exact number varies with matrix size and number of partials, but the workaround is systematically faster.
sing ForwardDiff
using LinearAlgebra
# Build random Dual-valued matrices.
function random_dual_matrix(m, n, N)
[ForwardDiff.Dual(rand(), ntuple(_ -> rand(), N)) for _ in 1:m, _ in 1:n]
end
# BLAS-capable baseline for Dual matrices: split into value and each partial,
# call ordinary BLAS-backed mul! on the plain arrays, and reassemble the result.
function mul_dual_blas!(C, A, B)
N = ForwardDiff.npartials(eltype(A))
A_val = ForwardDiff.value.(A)
B_val = ForwardDiff.value.(B)
A_parts = ntuple(i -> ForwardDiff.partials.(A, i), N)
B_parts = ntuple(i -> ForwardDiff.partials.(B, i), N)
C_val = ForwardDiff.value.(C)
C_parts = ntuple(i -> ForwardDiff.partials.(C, i), N)
mul!(C_val, A_val, B_val) # BLAS for values
for i in 1:N
# ∂C_i = ∂A_i * B + A * ∂B_i
mul!(C_parts[i], A_parts[i], B_val)
mul!(C_parts[i], A_val, B_parts[i], true, true)
end
# Reconstruct C from value and partials.
for j in 1:size(C, 2), i in 1:size(C, 1)
C[i, j] = ForwardDiff.Dual(
C_val[i, j],
ForwardDiff.Partials(ntuple(l -> C_parts[l][i, j], N))
)
end
C
end
N_partials = 3
m, n, k = 500, 400, 300
A = random_dual_matrix(m, k, N_partials)
B = random_dual_matrix(k, n, N_partials)
C_generic = similar(A, m, n)
C_blas = similar(A, m, n)
# Warm up both paths before timing.
mul!(C_generic, A, B)
mul!(C_generic, A, B)
mul_dual_blas!(C_blas, A, B)
mul_dual_blas!(C_blas, A, B)
# Time generic 3-arg mul!.
println("Timing generic 3-arg mul! with Matrix{Dual}...")
t_generic = minimum(@elapsed mul!(C_generic, A, B) for _ in 1:5)
# Time BLAS-based baseline.
println("Timing BLAS-based baseline...")
t_blas = minimum(@elapsed mul_dual_blas!(C_blas, A, B) for _ in 1:5)
println()
println("Generic mul! : $(round(t_generic; digits=4)) s")
println("BLAS baseline: $(round(t_blas; digits=4)) s")
println("Speedup : $(round(t_generic / t_blas; digits=2))x")
# Verify correctness.
mul!(C_generic, A, B)
mul_dual_blas!(C_blas, A, B)
@assert C_generic ≈ C_blas
println()
println("Results match.")
- Lingua principale
- Julia
- Stelle
- 1k
- Fork
- 162
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Preparare l'ambiente
Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di JuliaDiff/ForwardDiff.jl
-
missing floatmin, floatmax, and issubnormal for DualForse già presa @devmotion l’ha presa 14 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
JuliaDiff/ForwardDiff.jl#852 · 6 commenti ·
-
Enable NaN-safe mode by defaultForse già presa @devmotion l’ha presa 1 giorno fa. Aperta
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
JuliaDiff/ForwardDiff.jl#857 · 1 reazione ·
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 75/100
JuliaDiff/ForwardDiff.jl#849 ·
-
`gradient!` leaves part of the result unwritten when `f` does not depend on its argumentForse già presa @devmotion l’ha presa 43 giorni fa. Aperta
Difficoltà 3/5 1-2 giorni Idoneità per principianti 78/100
JuliaDiff/ForwardDiff.jl#847 ·
-
hessian!(::ImmutableDiffResult, f, ::StaticArray) errors when f does not depend on its argumentForse già presa @devmotion l’ha presa 44 giorni fa. Aperta
Difficoltà 3/5 1-2 giorni Idoneità per principianti 76/100
JuliaDiff/ForwardDiff.jl#846 · 1 commento ·
Tutte le issue di JuliaDiff/ForwardDiff.jl
Issue simili
-
documentation
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 Meno di un'ora Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
CliMA/ClimaAtmos.jl#4891 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
tagbot-manual
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 75/100
tshort/StaticCompiler.jl#191 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
SciML/SciMLSensitivity.jl#1703 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno