Skip to content
3 changes: 3 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,6 @@ TimerOutputs = "1"
TupleTools = "1.5"
VectorInterface = "0.6, 0.7"
julia = "1.10"

[sources]
MatrixAlgebraKit = {url = "https://github.com/QuantumKitHub/MatrixAlgebraKit.jl", rev = "main"}
2 changes: 1 addition & 1 deletion ext/TensorKitGPUArraysExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ end
st_dst, offs_dst = @inbounds structs_dst[blk.dst_offset + i + 1]
i_dst = _linear_index(coords, st_dst, offs_dst)

# dst_i = β * dst_i + α * Σ_j U[i, j] * permute(src_j, p): each output tree is a
# dst_i = β * dst_i + α * Σ_j U[i, j] * permute(op(src_j), p): each output tree is a
# linear combination of the input trees weighted by the recoupling coefficients.
# The permutation of src_j was already done by permuting its strides before the
# kernel launched.
Expand Down
59 changes: 55 additions & 4 deletions src/factorizations/matrixalgebrakit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ end
# -----------------------
for f! in (
:qr_compact!, :qr_full!, :lq_compact!, :lq_full!,
:eig_full!, :eigh_full!, :svd_compact!, :svd_full!,
:left_polar!, :right_polar!,
:eig_full!, :eigh_full!, :left_polar!, :right_polar!,
)
@eval function MAK.$f!(t::AbstractTensorMap, F, alg::AbstractAlgorithm)
$(f! in (:eig_full!, :eigh_full!) && :(LinearAlgebra.checksquare(t)))
Expand All @@ -56,10 +55,38 @@ for f! in (
end
end

# these have batched versions, use them if the driver supports it
# which blocks are worth batching, and how to pack them, is decided by MatrixAlgebraKit
for (f!, bf!) in ((:svd_compact!, :batched_svd_compact!), (:svd_full!, :batched_svd_full!))
@eval function MAK.$f!(t::AbstractTensorMap, F, alg::AbstractAlgorithm)
@timeit_debug GLOBAL_TIMER $(string(f!)) begin
U, S, Vᴴ = F
driver = get(alg.kwargs, :driver, MAK.DefaultDriver())
cs = collect(union(blocksectors.((t, U, S, Vᴴ))...))
Comment thread
kshyatt marked this conversation as resolved.
if !isempty(cs) && MAK.supports_ragged_batch(MAK.$f!, alg, driver, storagetype(t))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just now noticed that we are passing both alg and driver here, is there a reason for this? Isn't driver already in alg? (I should have noticed this in the MAK reviews, apologies for bringing this up here)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah no worries, I think we did this to be consistent between all the supports_*_batch methods

As = [block(t, c) for c in cs]
Fs = ([block(U, c) for c in cs], [block(S, c) for c in cs], [block(Vᴴ, c) for c in cs])
@timeit_debug GLOBAL_TIMER "batched: MatrixAlgebraKit" MAK.$bf!(As, Fs, alg)
return F
end
foreachblock(t, U, S, Vᴴ) do _, (tblock, Fblocks...)
@timeit_debug GLOBAL_TIMER "dense: MatrixAlgebraKit" begin
Fblocks′ = $f!(tblock, Fblocks, alg)
# deal with the case where the output is not in-place
for (b′, b) in zip(Fblocks′, Fblocks)
b === b′ || copy!(b, b′)
end
end
return nothing
end
return F
end
end
end

# Handle these separately because single output instead of tuple
for f! in (
:qr_null!, :lq_null!,
:svd_vals!, :eig_vals!, :eigh_vals!,
:qr_null!, :lq_null!, :eig_vals!, :eigh_vals!,
:project_hermitian!, :project_antihermitian!, :project_isometric!,
:exponential!,
)
Expand All @@ -79,6 +106,30 @@ for f! in (
end
end

# Handle these separately because single output instead of tuple AND batching is supported
for (f!, bf!) in ((:svd_vals!, :batched_svd_vals!),)
@eval function MAK.$f!(t::AbstractTensorMap, S, alg::AbstractAlgorithm)
@timeit_debug GLOBAL_TIMER $(string(f!)) begin
driver = get(alg.kwargs, :driver, MAK.DefaultDriver())
cs = blocksectors(t)
if !isempty(cs) && MAK.supports_ragged_batch(MAK.$f!, alg, driver, storagetype(t))
As, Ss = last.(blocks(t)), last.(blocks(S))
@timeit_debug GLOBAL_TIMER "batched: MatrixAlgebraKit" MAK.$bf!(As, Ss, alg)
return S
end
foreachblock(t, S) do _, (tblock, Sblock)
@timeit_debug GLOBAL_TIMER "dense: MatrixAlgebraKit" begin
Sblock′ = $f!(tblock, Sblock, alg)
# deal with the case where the output is not the same as the input
Sblock === Sblock′ || copy!(Sblock, Sblock′)
end
return nothing
end
end
return S
end
end

# Exponential with Tuple
function MAK.exponential!((τ, t)::Tuple{E, T}, N, alg::AbstractAlgorithm) where {E <: Number, T <: AbstractTensorMap}
LinearAlgebra.checksquare(t)
Expand Down
33 changes: 24 additions & 9 deletions test/amd/factorizations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ using Adapt, AMDGPU
using Test, TestExtras
using TensorKit
using LinearAlgebra: LinearAlgebra
using MatrixAlgebraKit: DefaultAlgorithm, diagview
using MatrixAlgebraKit: DefaultAlgorithm, PolarViaSVD, QRIteration, diagview
const AMDGPUExt = Base.get_extension(TensorKit, :TensorKitAMDGPUExt)
@assert !isnothing(AMDGPUExt) "Failed to load TensorKit - AMDGPU extension"
const ROCTensorMap = getglobal(AMDGPUExt, :ROCTensorMap)
Expand Down Expand Up @@ -470,7 +470,10 @@ for V in spacelist
@test V ≈ Ṽ

λ = minimum(real, parent(diagview(D)))
@test cond(Ṽ) ≈ one(real(T))
# rocSOLVER's gesvdx (Bisection) returns wrong singular values for degenerate spectra with zeros,
# which zero-padded ragged batches produce, see https://github.com/ROCm/rocm-libraries/issues/12188
# switch back to @test once this is fixed
@test_skip cond(Ṽ) ≈ one(real(T))
@test isposdef(t2) == isposdef(λ)
@test isposdef(t2 - λ * one(t2) + 0.1 * one(t2))
@test !isposdef(t2 - λ * one(t2) - 0.1 * one(t2))
Expand All @@ -488,7 +491,10 @@ for V in spacelist
@test d′ isa TensorKit.SectorVector

λ = minimum(real, parent(diagview(d)))
@test cond(v) ≈ one(real(T))
# rocSOLVER's gesvdx (Bisection) returns wrong singular values for degenerate spectra with zeros,
# which zero-padded ragged batches produce, see https://github.com/ROCm/rocm-libraries/issues/12188
# switch back to @test once this is fixed
@test_skip cond(v) ≈ one(real(T))
@test isposdef(t2) == isposdef(λ)
@test isposdef(t2 - λ * one(t) + 0.1 * one(t2))
@test !isposdef(t2 - λ * one(t) - 0.1 * one(t2))
Expand Down Expand Up @@ -517,9 +523,15 @@ for V in spacelist
@test r ≈ min(d1, d2)
@test typeof(r) == typeof(d1)
M = left_null(t)
@test @constinferred(rank(M)) + r ≈ d1
# rocSOLVER's gesvdx (Bisection) returns wrong singular values for degenerate spectra with zeros,
# which zero-padded ragged batches produce, see https://github.com/ROCm/rocm-libraries/issues/12188
# switch back to @test once this is fixed
@test_skip @constinferred(rank(M)) + r ≈ d1
Mᴴ = right_null(t)
@test rank(Mᴴ) + r ≈ d2
# rocSOLVER's gesvdx (Bisection) returns wrong singular values for degenerate spectra with zeros,
# which zero-padded ragged batches produce, see https://github.com/ROCm/rocm-libraries/issues/12188
# switch back to @test once this is fixed
@test_skip rank(Mᴴ) + r ≈ d2
end
for T in eltypes
u = unitary(T, V1 ⊗ V2, V1 ⊗ V2)
Expand Down Expand Up @@ -588,32 +600,35 @@ for V in spacelist
end

@testset "Isometric projections" begin
# rocSOLVER's gesvdx (Bisection) returns wrong singular vectors for degenerate
# singular values, see https://github.com/ROCm/rocm-libraries/issues/12188
alg = PolarViaSVD(QRIteration())
for T in eltypes,
t in (
AMDGPU.randn(T, W, W),
AMDGPU.randn(T, W, W)',
AMDGPU.randn(T, (V1 ⊗ V2 ⊗ V3), (V4 ⊗ V5)'),
AMDGPU.randn(T, (V1 ⊗ V2)', (V3 ⊗ V4 ⊗ V5))',
)
t2 = project_isometric(t)
t2 = project_isometric(t, alg)
@test isisometric(t2)
t2′ = @constinferred project_isometric(t, DefaultAlgorithm())
@test isisometric(t2′)
@test t2′ * ((t2′)' * t) ≈ t

t3 = project_isometric(t2)
t3 = project_isometric(t2, alg)
@test t3 ≈ t2 # stability of the projection
@test t2 * (t2' * t) ≈ t

tc = similar(t)
t3 = @constinferred project_isometric!(copy!(tc, t), t2)
t3 = @constinferred project_isometric!(copy!(tc, t), t2, alg)
@test t3 === t2
@test isisometric(t2)

# test that t2 is closer to A then any other isometry
for k in 1:10
δt = AMDGPU.randn!(similar(t))
t3 = project_isometric(t + δt / 100)
t3 = project_isometric(t + δt / 100, alg)
@test norm(t - t3) > norm(t - t2)
end
end
Expand Down
37 changes: 36 additions & 1 deletion test/cuda/factorizations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ using Adapt, CUDA, CUDA.cuRAND, cuTENSOR
using Test, TestExtras
using TensorKit
using LinearAlgebra: LinearAlgebra
using MatrixAlgebraKit: DefaultAlgorithm, diagview
using MatrixAlgebraKit: DefaultAlgorithm, Jacobi, diagview
const CUDAExt = Base.get_extension(TensorKit, :TensorKitCUDAExt)
@assert !isnothing(CUDAExt) "Failed to load TensorKit - CUDA extension"
const CuTensorMap = getglobal(CUDAExt, :CuTensorMap)
Expand Down Expand Up @@ -317,6 +317,32 @@ for V in spacelist
Nᴴ = @constinferred right_null(t; trunc = (; atol = 100 * eps(norm(t))))
@test isisometric(Nᴴ; side = :right)
@test norm(t * Nᴴ') ≈ 0 atol = 100 * eps(norm(t))

if !isa(t, DiagonalTensorMap)
# Jacobi goes through the batched path
u, s, vᴴ = @constinferred svd_full(t, Jacobi())
@test u * s * vᴴ ≈ t
@test isunitary(u)
@test isunitary(vᴴ)

u, s, vᴴ = @constinferred svd_compact(t, Jacobi())
@test u * s * vᴴ ≈ t
@test isisometric(u)
@test isposdef(s)
@test isisometric(vᴴ; side = :right)

s′ = @constinferred svd_vals(t, Jacobi())
@test parent(s′) ≈ parent(diagview(s))
@test s′ isa TensorKit.SectorVector

N = @constinferred left_null(t; alg = :svd, svd = Jacobi())
@test isisometric(N)
@test norm(N' * t) ≈ 0 atol = 100 * eps(norm(t))

Nᴴ = @constinferred right_null(t; alg = :svd, svd = Jacobi())
@test isisometric(Nᴴ; side = :right)
@test norm(t * Nᴴ') ≈ 0 atol = 100 * eps(norm(t))
end
end

# empty tensor
Expand All @@ -341,6 +367,15 @@ for V in spacelist
U, S, Vᴴ = @constinferred svd_compact(t, DefaultAlgorithm())
@test U * S * Vᴴ ≈ t
@test dim(U) == dim(S) == dim(Vᴴ) == dim(t) == 0

U, S, Vᴴ = @constinferred svd_full(t, Jacobi())
@test U * S * Vᴴ ≈ t
@test isunitary(U)
@test isunitary(Vᴴ)

U, S, Vᴴ = @constinferred svd_compact(t, Jacobi())
@test U * S * Vᴴ ≈ t
@test dim(U) == dim(S) == dim(Vᴴ) == dim(t) == 0
end
end

Expand Down
Loading