Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8df520e
try to make truncation GPU-friendly
lkdvos Jan 8, 2026
1481184
Temporarily fix StridedViews version
kshyatt Jan 20, 2026
1e46b0d
Revert "Temporarily fix StridedViews version"
kshyatt Jan 20, 2026
8ef0425
Small update for diagonal pullbacks
kshyatt Jan 22, 2026
8423ce8
Fix last error
kshyatt Jan 22, 2026
848f0cc
Reenable truncated CUDA tests
kshyatt Jan 22, 2026
7ae9b05
make truncation run on GPU
lkdvos Jan 22, 2026
b1fe3bd
bypass scalar indexing by specializing
lkdvos Jan 22, 2026
94ecfca
convenience overloads
lkdvos Jan 22, 2026
7395b8a
gpu-friendly copies
lkdvos Jan 22, 2026
9af19b7
retain storagetype in extended_S
lkdvos Jan 22, 2026
180afe6
avoid GPU issues with truncated adjoint tensormaps
lkdvos Jan 22, 2026
efbe088
various utility improvements
lkdvos Jan 22, 2026
eafd7a8
complete rewrite of implementation
lkdvos Jan 22, 2026
f4892cf
GPU doesn't like `trues`
lkdvos Jan 22, 2026
3f273a1
remove CUDA specializations and temporarily add missing MatrixAlgebra…
lkdvos Jan 22, 2026
6842a70
better dimension testing
lkdvos Jan 22, 2026
5bb2a23
fix unbound type parameter
lkdvos Jan 22, 2026
ddd0ed6
add missing import
lkdvos Jan 22, 2026
f3b45ef
be careful about double method definitions
lkdvos Jan 22, 2026
f26cffe
disable diagonal test
lkdvos Jan 23, 2026
6ff9ac8
bump MatrixAlgebraKit dependency
lkdvos Jan 23, 2026
6666459
Revert "disable diagonal test"
lkdvos Jan 23, 2026
ebbdb84
remove unnecessary specializations
lkdvos Jan 23, 2026
2d7338a
specialize CPU implementations
lkdvos Jan 23, 2026
bde9c50
add explanation TruncationByOrder
lkdvos Jan 26, 2026
d603b9e
add explanation and specialization TruncationByError
lkdvos Jan 26, 2026
5bc1506
fix stupidity
lkdvos Jan 26, 2026
b44878f
fix views
lkdvos Jan 27, 2026
4a722ef
enfore positive and finite p-norms
lkdvos Jan 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ FiniteDifferences = "0.12"
GPUArrays = "11.3.1"
LRUCache = "1.0.2"
LinearAlgebra = "1"
MatrixAlgebraKit = "0.6.2"
MatrixAlgebraKit = "0.6.3"
Mooncake = "0.4.183"
OhMyThreads = "0.8.0"
Printf = "1"
Expand Down
1 change: 1 addition & 0 deletions ext/TensorKitCUDAExt/TensorKitCUDAExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ using TensorKit: MatrixAlgebraKit
using Random

include("cutensormap.jl")
include("truncation.jl")

end
26 changes: 26 additions & 0 deletions ext/TensorKitCUDAExt/truncation.jl
Comment thread
lkdvos marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const CuSectorVector{T, I} = TensorKit.SectorVector{T, I, <:CuVector{T}}

function MatrixAlgebraKit.findtruncated(
values::CuSectorVector, strategy::MatrixAlgebraKit.TruncationByOrder
)
I = sectortype(values)

dims = similar(values, Base.promote_op(dim, I))
for (c, v) in pairs(dims)
fill!(v, dim(c))
end

perm = sortperm(parent(values); strategy.by, strategy.rev)
cumulative_dim = cumsum(Base.permute!(parent(dims), perm))

result = similar(values, Bool)
parent(result)[perm] .= cumulative_dim .<= strategy.howmany
return result
end

# Needed until MatrixAlgebraKit patch hits...
Comment thread
kshyatt marked this conversation as resolved.
function MatrixAlgebraKit._ind_intersect(A::CuVector{Bool}, B::CuVector{Int})
result = fill!(similar(A), false)
result[B] .= @view A[B]
Comment thread
kshyatt marked this conversation as resolved.
return result
end
25 changes: 21 additions & 4 deletions src/factorizations/adjoint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ _adjoint(alg::MAK.LAPACK_HouseholderLQ) = MAK.LAPACK_HouseholderQR(; alg.kwargs.
_adjoint(alg::MAK.LAPACK_HouseholderQL) = MAK.LAPACK_HouseholderRQ(; alg.kwargs...)
_adjoint(alg::MAK.LAPACK_HouseholderRQ) = MAK.LAPACK_HouseholderQL(; alg.kwargs...)
_adjoint(alg::MAK.PolarViaSVD) = MAK.PolarViaSVD(_adjoint(alg.svd_alg))
_adjoint(alg::TruncatedAlgorithm) = TruncatedAlgorithm(_adjoint(alg.alg), alg.trunc)
_adjoint(alg::AbstractAlgorithm) = alg

_adjoint(alg::MAK.CUSOLVER_HouseholderQR) = MAK.LQViaTransposedQR(alg)
Expand Down Expand Up @@ -81,7 +82,7 @@ for (left_f, right_f) in zip(
end

# 3-arg functions
for f in (:svd_full, :svd_compact)
for f in (:svd_full, :svd_compact, :svd_trunc)
Comment thread
kshyatt marked this conversation as resolved.
f! = Symbol(f, :!)
@eval function MAK.copy_input(::typeof($f), t::AdjointTensorMap)
return adjoint(MAK.copy_input($f, adjoint(t)))
Expand All @@ -93,9 +94,16 @@ for f in (:svd_full, :svd_compact)
return reverse(adjoint.(MAK.initialize_output($f!, adjoint(t), _adjoint(alg))))
end

@eval function MAK.$f!(t::AdjointTensorMap, F, alg::AbstractAlgorithm)
F′ = $f!(adjoint(t), reverse(adjoint.(F)), _adjoint(alg))
return reverse(adjoint.(F′))
if f === :svd_trunc
Comment thread
kshyatt marked this conversation as resolved.
function MAK.svd_trunc!(t::AdjointTensorMap, F, alg::AbstractAlgorithm)
U, S, Vᴴ, ϵ = svd_trunc!(adjoint(t), reverse(adjoint.(F)), _adjoint(alg))
return Vᴴ', S, U', ϵ
end
else
@eval function MAK.$f!(t::AdjointTensorMap, F, alg::AbstractAlgorithm)
F′ = $f!(adjoint(t), reverse(adjoint.(F)), _adjoint(alg))
return reverse(adjoint.(F′))
end
end

# disambiguate by prohibition
Expand All @@ -111,6 +119,15 @@ function MAK.svd_compact!(t::AdjointTensorMap, F, alg::DiagonalAlgorithm)
F′ = svd_compact!(adjoint(t), reverse(adjoint.(F)), _adjoint(alg))
return reverse(adjoint.(F′))
end
function MAK.initialize_output(
::typeof(svd_trunc!), t::AdjointTensorMap, alg::TruncatedAlgorithm
Comment thread
kshyatt marked this conversation as resolved.
)
return reverse(adjoint.(MAK.initialize_output(svd_trunc!, adjoint(t), _adjoint(alg))))
end
function MAK.svd_trunc!(t::AdjointTensorMap, F, alg::TruncatedAlgorithm)
U, S, Vᴴ, ϵ = svd_trunc!(adjoint(t), reverse(adjoint.(F)), _adjoint(alg))
return Vᴴ', S, U', ϵ
end

function LinearAlgebra.isposdef(t::AdjointTensorMap)
return isposdef(adjoint(t))
Expand Down
22 changes: 1 addition & 21 deletions src/factorizations/diagonal.jl
Comment thread
lkdvos marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,6 @@ for f in (
@eval MAK.copy_input(::typeof($f), d::DiagonalTensorMap) = copy(d)
end

for f! in (:eig_full!, :eig_trunc!)
@eval function MAK.initialize_output(
::typeof($f!), d::AbstractTensorMap, ::DiagonalAlgorithm
)
return d, similar(d)
end
end

for f! in (:eigh_full!, :eigh_trunc!)
@eval function MAK.initialize_output(
::typeof($f!), d::AbstractTensorMap, ::DiagonalAlgorithm
)
if scalartype(d) <: Real
return d, similar(d, space(d))
else
return similar(d, real(scalartype(d))), similar(d, space(d))
end
end
end

for f! in (:qr_full!, :qr_compact!)
@eval function MAK.initialize_output(
::typeof($f!), d::AbstractTensorMap, ::DiagonalAlgorithm
Expand Down Expand Up @@ -93,7 +73,7 @@ end
# For diagonal inputs we don't have to promote the scalartype since we know they are symmetric
function MAK.initialize_output(::typeof(eig_vals!), t::AbstractTensorMap, alg::DiagonalAlgorithm)
V_D = fuse(domain(t))
Tc = scalartype(t)
Tc = complex(scalartype(t))
A = similarstoragetype(t, Tc)
return SectorVector{Tc, sectortype(t), A}(undef, V_D)
end
162 changes: 67 additions & 95 deletions src/factorizations/truncation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,36 @@ end
# ---------
_blocklength(d::Integer, ind) = _blocklength(Base.OneTo(d), ind)
_blocklength(ax, ind) = length(ax[ind])
_blocklength(ax::Base.OneTo, ind::AbstractVector{<:Integer}) = length(ind)
_blocklength(ax::Base.OneTo, ind::AbstractVector{Bool}) = count(ind)

function truncate_space(V::ElementarySpace, inds)
return spacetype(V)(c => _blocklength(dim(V, c), ind) for (c, ind) in inds)
return spacetype(V)(c => _blocklength(dim(V, c), ind) for (c, ind) in pairs(inds))
end

function truncate_domain!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, inds)
for (c, b) in blocks(tdst)
I = get(inds, c, nothing)
@assert !isnothing(I)
copy!(b, view(block(tsrc, c), :, I))
b′ = block(tsrc, c)
b .= b′[:, I]
Comment thread
lkdvos marked this conversation as resolved.
Outdated
end
return tdst
end
function truncate_codomain!(tdst::AbstractTensorMap, tsrc::AbstractTensorMap, inds)
for (c, b) in blocks(tdst)
I = get(inds, c, nothing)
@assert !isnothing(I)
copy!(b, view(block(tsrc, c), I, :))
b′ = block(tsrc, c)
b .= b′[I, :]
Comment thread
lkdvos marked this conversation as resolved.
Outdated
end
return tdst
end
function truncate_diagonal!(Ddst::DiagonalTensorMap, Dsrc::DiagonalTensorMap, inds)
for (c, b) in blocks(Ddst)
I = get(inds, c, nothing)
@assert !isnothing(I)
copy!(diagview(b), view(diagview(block(Dsrc, c)), I))
diagview(b) .= @view diagview(block(Dsrc, c))[I]
Comment thread
lkdvos marked this conversation as resolved.
Outdated
end
return Ddst
end
Expand All @@ -78,7 +83,7 @@ end
function MAK.truncate(
::typeof(left_null!), (U, S)::NTuple{2, AbstractTensorMap}, strategy::TruncationStrategy
)
extended_S = zerovector!(SectorVector{eltype(S)}(undef, fuse(codomain(U))))
extended_S = zerovector!(SectorVector{eltype(S), sectortype(S), storagetype(S)}(undef, fuse(codomain(U))))
for (c, b) in blocks(S)
copyto!(extended_S[c], diagview(b)) # copyto! since `b` might be shorter
end
Expand All @@ -91,7 +96,7 @@ end
function MAK.truncate(
::typeof(right_null!), (S, Vᴴ)::NTuple{2, AbstractTensorMap}, strategy::TruncationStrategy
)
extended_S = zerovector!(SectorVector{eltype(S)}(undef, fuse(domain(Vᴴ))))
extended_S = zerovector!(SectorVector{eltype(S), sectortype(S), storagetype(S)}(undef, fuse(domain(Vᴴ))))
for (c, b) in blocks(S)
copyto!(extended_S[c], diagview(b)) # copyto! since `b` might be shorter
end
Expand Down Expand Up @@ -142,57 +147,11 @@ for f! in (:eig_trunc!, :eigh_trunc!)
end
end

# Find truncation
# ---------------
# findtruncated
# -------------
# auxiliary functions
rtol_to_atol(S, p, atol, rtol) = rtol == 0 ? atol : max(atol, norm(S, p) * rtol)

function _compute_truncerr(Σdata, truncdim, p = 2)
I = keytype(Σdata)
S = scalartype(valtype(Σdata))
return TensorKit._norm(
(c => @view(v[(get(truncdim, c, 0) + 1):end]) for (c, v) in Σdata),
p, zero(S)
)
end

function _findnexttruncvalue(
S, truncdim::SectorDict{I, Int}; by = identity, rev::Bool = true
) where {I <: Sector}
# early return
(isempty(S) || all(iszero, values(truncdim))) && return nothing
if rev
σmin, imin = findmin(keys(truncdim)) do c
d = truncdim[c]
return by(S[c][d])
end
return σmin, keys(truncdim)[imin]
else
σmax, imax = findmax(keys(truncdim)) do c
d = truncdim[c]
return by(S[c][d])
end
return σmax, keys(truncdim)[imax]
end
end

function _sort_and_perm(values::SectorVector; by = identity, rev::Bool = false)
values_sorted = similar(values)
perms = SectorDict(
(
begin
p = sortperm(v; by, rev)
vs = values_sorted[c]
vs .= view(v, p)
c => p
end
) for (c, v) in pairs(values)
)
return values_sorted, perms
end

# findtruncated
# -------------
# Generic fallback
function MAK.findtruncated_svd(values::SectorVector, strategy::TruncationStrategy)
return MAK.findtruncated(values, strategy)
Expand All @@ -203,24 +162,38 @@ function MAK.findtruncated(values::SectorVector, ::NoTruncation)
end

function MAK.findtruncated(values::SectorVector, strategy::TruncationByOrder)
values_sorted, perms = _sort_and_perm(values; strategy.by, strategy.rev)
inds = MAK.findtruncated_svd(values_sorted, truncrank(strategy.howmany))
return SectorDict(c => perms[c][I] for (c, I) in inds)
end
function MAK.findtruncated_svd(values::SectorVector, strategy::TruncationByOrder)
I = keytype(values)
truncdim = SectorDict{I, Int}(c => length(d) for (c, d) in pairs(values))
totaldim = sum(dim(c) * d for (c, d) in truncdim; init = 0)
while totaldim > strategy.howmany
next = _findnexttruncvalue(values, truncdim; strategy.by, strategy.rev)
isnothing(next) && break
_, cmin = next
truncdim[cmin] -= 1
totaldim -= dim(cmin)
truncdim[cmin] == 0 && delete!(truncdim, cmin)
I = sectortype(values)


if FusionStyle(I) isa UniqueFusion # dimensions are all 1
Comment thread
kshyatt marked this conversation as resolved.
Outdated
perm = partialsortperm(parent(values), 1:strategy.howmany; strategy.by, strategy.rev)
result = similar(values, Bool)
fill!(parent(result), false)
parent(result)[perm] .= true
return result
end

dims = similar(values, Base.promote_op(dim, I))
for (c, v) in pairs(dims)
fill!(v, dim(c))
end

perm = sortperm(parent(values); strategy.by, strategy.rev)
result = similar(values, Bool)
fill!(parent(result), false)

totaldim = 0
for i in perm
totaldim += dims[i]
totaldim > strategy.howmany && break
result[i] = true
end
return SectorDict(c => Base.OneTo(d) for (c, d) in truncdim)

return result
end
# disambiguate
MAK.findtruncated_svd(values::SectorVector, strategy::TruncationByOrder) =
MAK.findtruncated(values, strategy)

function MAK.findtruncated(values::SectorVector, strategy::TruncationByFilter)
return SectorDict(c => findall(strategy.filter, d) for (c, d) in pairs(values))
Expand All @@ -237,28 +210,29 @@ function MAK.findtruncated_svd(values::SectorVector, strategy::TruncationByValue
return SectorDict(c => MAK.findtruncated_svd(d, strategy′) for (c, d) in pairs(values))
end

function MAK.findtruncated(values::SectorVector, strategy::TruncationByError)
values_sorted, perms = _sort_and_perm(values; strategy.by, strategy.rev)
inds = MAK.findtruncated_svd(values_sorted, truncrank(strategy.howmany))
return SectorDict(c => perms[c][I] for (c, I) in inds)
end
function MAK.findtruncated_svd(values::SectorVector, strategy::TruncationByError)
I = keytype(values)
truncdim = SectorDict{I, Int}(c => length(d) for (c, d) in pairs(values))
by(c, v) = abs(v)^strategy.p * dim(c)
Nᵖ = sum(((c, v),) -> sum(Base.Fix1(by, c), v), pairs(values))
ϵᵖ = max(strategy.atol^strategy.p, strategy.rtol^strategy.p * Nᵖ)
truncerrᵖ = zero(real(scalartype(valtype(values))))
next = _findnexttruncvalue(values, truncdim)
while !isnothing(next)
σmin, cmin = next
truncerrᵖ += by(cmin, σmin)
truncerrᵖ >= ϵᵖ && break
(truncdim[cmin] -= 1) == 0 && delete!(truncdim, cmin)
next = _findnexttruncvalue(values, truncdim)
function MAK.findtruncated(values::SectorVector, strategy::MAK.TruncationByError)
ϵᵖmax = max(strategy.atol^strategy.p, strategy.rtol^strategy.p * norm(values, strategy.p))
ϵᵖ = similar(values, typeof(ϵᵖmax))

if FusionStyle(sectortype(values)) isa UniqueFusion # dimensions are all 1
parent(ϵᵖ) .= abs.(parent(values)) .^ strategy.p
else
for (c, v) in pairs(values)
v′ = ϵᵖ[c]
v′ .= abs.(v) .^ strategy.p .* dim(c)
end
end
return SectorDict{I, Base.OneTo{Int}}(c => Base.OneTo(d) for (c, d) in truncdim)

perm = sortperm(parent(values); by = abs, rev = false)
cumulative_err = cumsum(Base.permute!(parent(ϵᵖ), perm))

result = similar(values, Bool)
parent(result)[perm] .= cumulative_err .> ϵᵖmax
return result
end
# disambiguate
MAK.findtruncated_svd(values::SectorVector, strategy::TruncationByError) =
MAK.findtruncated(values, strategy)

function MAK.findtruncated(values::SectorVector, strategy::TruncationSpace)
blockstrategy(c) = truncrank(dim(strategy.space, c); strategy.by, strategy.rev)
Expand All @@ -273,17 +247,15 @@ function MAK.findtruncated(values::SectorVector, strategy::TruncationIntersectio
inds = map(Base.Fix1(MAK.findtruncated, values), strategy.components)
return SectorDict(
c => mapreduce(
Base.Fix2(getindex, c), MatrixAlgebraKit._ind_intersect, inds;
init = trues(length(values[c]))
Base.Fix2(getindex, c), MatrixAlgebraKit._ind_intersect, inds
) for c in intersect(map(keys, inds)...)
)
end
function MAK.findtruncated_svd(values::SectorVector, strategy::TruncationIntersection)
inds = map(Base.Fix1(MAK.findtruncated_svd, values), strategy.components)
return SectorDict(
c => mapreduce(
Base.Fix2(getindex, c), MatrixAlgebraKit._ind_intersect, inds;
init = trues(length(values[c]))
Base.Fix2(getindex, c), MatrixAlgebraKit._ind_intersect, inds
) for c in intersect(map(keys, inds)...)
)
end
Expand All @@ -293,7 +265,7 @@ end
MAK.truncation_error(values::SectorVector, ind) = MAK.truncation_error!(copy(values), ind)

function MAK.truncation_error!(values::SectorVector, ind)
for (c, ind_c) in ind
for (c, ind_c) in pairs(ind)
v = values[c]
v[ind_c] .= zero(eltype(v))
end
Expand Down
Loading