-
Notifications
You must be signed in to change notification settings - Fork 57
Basic tensor support for AMDGPU #341
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
4f7abbd
f0ee0ef
6877f19
3c9ddaf
6bb113b
a8ff39d
cedf57d
569098b
e899dee
b883387
9b4a863
0f03952
bcefc45
8f81627
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,20 @@ | ||||||
| module TensorKitAMDGPUExt | ||||||
|
|
||||||
| using AMDGPU, AMDGPU.rocBLAS, AMDGPU.rocSOLVER, LinearAlgebra | ||||||
| using AMDGPU: @allowscalar | ||||||
| import AMDGPU: rand as rocrand, rand! as rocrand!, randn as rocrandn, randn! as rocrandn! | ||||||
|
|
||||||
| using TensorKit | ||||||
| using TensorKit.Factorizations | ||||||
| using TensorKit.Strided | ||||||
| using TensorKit.Factorizations: AbstractAlgorithm | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one is from |
||||||
| using TensorKit: SectorDict, tensormaptype, scalar, similarstoragetype, AdjointTensorMap, scalartype, project_symmetric_and_check | ||||||
| import TensorKit: randisometry, rand, randn | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
|
|
||||||
| using TensorKit: MatrixAlgebraKit | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| using Random | ||||||
|
|
||||||
| include("roctensormap.jl") | ||||||
|
|
||||||
| end | ||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,166 @@ | ||||||||||
| const ROCTensorMap{T, S, N₁, N₂} = TensorMap{T, S, N₁, N₂, ROCVector{T, AMDGPU.Mem.HIPBuffer}} | ||||||||||
| const ROCTensor{T, S, N} = ROCTensorMap{T, S, N, 0} | ||||||||||
|
|
||||||||||
| const AdjointROCTensorMap{T, S, N₁, N₂} = AdjointTensorMap{T, S, N₁, N₂, ROCTensorMap{T, S, N₁, N₂}} | ||||||||||
|
|
||||||||||
| function ROCTensorMap(t::TensorMap{T, S, N₁, N₂, A}) where {T, S, N₁, N₂, A} | ||||||||||
| return ROCTensorMap{T, S, N₁, N₂}(ROCArray{T}(t.data), space(t)) | ||||||||||
| end | ||||||||||
|
|
||||||||||
| # project_symmetric! doesn't yet work for GPU types, so do this on the host, then copy | ||||||||||
| function TensorKit.project_symmetric_and_check(::Type{T}, ::Type{A}, data::AbstractArray, V::TensorMapSpace; tol = sqrt(eps(real(float(eltype(data)))))) where {T, A <: ROCVector{T}} | ||||||||||
| h_t = TensorKit.TensorMapWithStorage{T, Vector{T}}(undef, V) | ||||||||||
| h_t = TensorKit.project_symmetric!(h_t, Array(data)) | ||||||||||
| # verify result | ||||||||||
| isapprox(Array(reshape(data, dims(h_t))), convert(Array, h_t); atol = tol) || | ||||||||||
| throw(ArgumentError("Data has non-zero elements at incompatible positions")) | ||||||||||
| return TensorKit.TensorMapWithStorage{T, A}(A(h_t.data), V) | ||||||||||
| end | ||||||||||
|
|
||||||||||
| for (fname, felt) in ((:zeros, :zero), (:ones, :one)) | ||||||||||
| @eval begin | ||||||||||
| function AMDGPU.$fname( | ||||||||||
| codomain::TensorSpace{S}, | ||||||||||
| domain::TensorSpace{S} = one(codomain) | ||||||||||
| ) where {S <: IndexSpace} | ||||||||||
| return AMDGPU.$fname(codomain ← domain) | ||||||||||
| end | ||||||||||
| function AMDGPU.$fname( | ||||||||||
| ::Type{T}, codomain::TensorSpace{S}, | ||||||||||
| domain::TensorSpace{S} = one(codomain) | ||||||||||
| ) where {T, S <: IndexSpace} | ||||||||||
| return AMDGPU.$fname(T, codomain ← domain) | ||||||||||
| end | ||||||||||
| AMDGPU.$fname(V::TensorMapSpace) = AMDGPU.$fname(Float64, V) | ||||||||||
| function AMDGPU.$fname(::Type{T}, V::TensorMapSpace) where {T} | ||||||||||
| t = ROCTensorMap{T}(undef, V) | ||||||||||
| fill!(t, $felt(T)) | ||||||||||
| return t | ||||||||||
| end | ||||||||||
| end | ||||||||||
| end | ||||||||||
|
|
||||||||||
| for randfun in (:rocrand, :rocrandn) | ||||||||||
| randfun! = Symbol(randfun, :!) | ||||||||||
| @eval begin | ||||||||||
| # converting `codomain` and `domain` into `HomSpace` | ||||||||||
| function $randfun( | ||||||||||
| codomain::TensorSpace{S}, | ||||||||||
| domain::TensorSpace{S} = one(codomain), | ||||||||||
| ) where {S <: IndexSpace} | ||||||||||
| return $randfun(codomain ← domain) | ||||||||||
| end | ||||||||||
| function $randfun( | ||||||||||
| ::Type{T}, codomain::TensorSpace{S}, | ||||||||||
| domain::TensorSpace{S} = one(codomain), | ||||||||||
| ) where {T, S <: IndexSpace} | ||||||||||
| return $randfun(T, codomain ← domain) | ||||||||||
| end | ||||||||||
| function $randfun( | ||||||||||
| rng::Random.AbstractRNG, ::Type{T}, | ||||||||||
| codomain::TensorSpace{S}, | ||||||||||
| domain::TensorSpace{S} = one(codomain), | ||||||||||
| ) where {T, S <: IndexSpace} | ||||||||||
| return $randfun(rng, T, codomain ← domain) | ||||||||||
| end | ||||||||||
|
|
||||||||||
| # filling in default eltype | ||||||||||
| $randfun(V::TensorMapSpace) = $randfun(Float64, V) | ||||||||||
| function $randfun(rng::Random.AbstractRNG, V::TensorMapSpace) | ||||||||||
| return $randfun(rng, Float64, V) | ||||||||||
| end | ||||||||||
|
|
||||||||||
| # filling in default rng | ||||||||||
| function $randfun(::Type{T}, V::TensorMapSpace) where {T} | ||||||||||
| return $randfun(Random.default_rng(), T, V) | ||||||||||
| end | ||||||||||
|
|
||||||||||
| # implementation | ||||||||||
| function $randfun( | ||||||||||
| rng::Random.AbstractRNG, ::Type{T}, | ||||||||||
| V::TensorMapSpace | ||||||||||
| ) where {T} | ||||||||||
| t = ROCTensorMap{T}(undef, V) | ||||||||||
| $randfun!(rng, t) | ||||||||||
| return t | ||||||||||
| end | ||||||||||
|
|
||||||||||
| function $randfun!(rng::Random.AbstractRNG, t::ROCTensorMap) | ||||||||||
| for (_, b) in blocks(t) | ||||||||||
| $randfun!(rng, b) | ||||||||||
| end | ||||||||||
|
Comment on lines
+89
to
+91
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Since we know this is |
||||||||||
| return t | ||||||||||
| end | ||||||||||
| end | ||||||||||
| end | ||||||||||
|
|
||||||||||
| # Scalar implementation | ||||||||||
| #----------------------- | ||||||||||
| function TensorKit.scalar(t::ROCTensorMap{T, S, 0, 0}) where {T, S} | ||||||||||
| inds = findall(!iszero, t.data) | ||||||||||
| return isempty(inds) ? zero(scalartype(t)) : @allowscalar @inbounds t.data[only(inds)] | ||||||||||
| end | ||||||||||
|
|
||||||||||
| function Base.convert( | ||||||||||
| TT::Type{ROCTensorMap{T, S, N₁, N₂}}, | ||||||||||
| t::AbstractTensorMap{<:Any, S, N₁, N₂} | ||||||||||
| ) where {T, S, N₁, N₂} | ||||||||||
| if typeof(t) === TT | ||||||||||
| return t | ||||||||||
| else | ||||||||||
| tnew = TT(undef, space(t)) | ||||||||||
| return copy!(tnew, t) | ||||||||||
| end | ||||||||||
| end | ||||||||||
|
|
||||||||||
| function LinearAlgebra.isposdef(t::ROCTensorMap) | ||||||||||
| domain(t) == codomain(t) || | ||||||||||
| throw(SpaceMismatch("`isposdef` requires domain and codomain to be the same")) | ||||||||||
| InnerProductStyle(spacetype(t)) === EuclideanInnerProduct() || return false | ||||||||||
| for (c, b) in blocks(t) | ||||||||||
| # do our own hermitian check | ||||||||||
| isherm = MatrixAlgebraKit.ishermitian(b) | ||||||||||
| isherm || return false | ||||||||||
| isposdef(Hermitian(b)) || return false | ||||||||||
| end | ||||||||||
| return true | ||||||||||
| end | ||||||||||
|
|
||||||||||
| function Base.promote_rule( | ||||||||||
| ::Type{<:TT₁}, | ||||||||||
| ::Type{<:TT₂} | ||||||||||
| ) where { | ||||||||||
| S, N₁, N₂, TTT₁, TTT₂, | ||||||||||
| TT₁ <: ROCTensorMap{TTT₁, S, N₁, N₂}, | ||||||||||
| TT₂ <: ROCTensorMap{TTT₂, S, N₁, N₂}, | ||||||||||
| } | ||||||||||
| T = TensorKit.VectorInterface.promote_add(TTT₁, TTT₂) | ||||||||||
| return ROCTensorMap{T, S, N₁, N₂} | ||||||||||
| end | ||||||||||
|
|
||||||||||
| # ROCTensorMap exponentation: | ||||||||||
| function TensorKit.exp!(t::ROCTensorMap) | ||||||||||
| domain(t) == codomain(t) || | ||||||||||
| error("Exponential of a tensor only exist when domain == codomain.") | ||||||||||
| !MatrixAlgebraKit.ishermitian(t) && throw(ArgumentError("`exp!` is currently only supported on hermitian AMDGPU tensors")) | ||||||||||
| for (c, b) in blocks(t) | ||||||||||
| copy!(b, parent(Base.exp(Hermitian(b)))) | ||||||||||
| end | ||||||||||
| return t | ||||||||||
| end | ||||||||||
|
|
||||||||||
| # functions that don't map ℝ to (a subset of) ℝ | ||||||||||
| for f in (:sqrt, :log, :asin, :acos, :acosh, :atanh, :acoth) | ||||||||||
| sf = string(f) | ||||||||||
| @eval function Base.$f(t::ROCTensorMap) | ||||||||||
| domain(t) == codomain(t) || | ||||||||||
| throw(SpaceMismatch("`$($sf)` of a tensor only exists when domain == codomain")) | ||||||||||
| !MatrixAlgebraKit.ishermitian(t) && throw(ArgumentError("`$($sf)` is currently only supported on hermitian AMDGPU tensors")) | ||||||||||
| T = complex(float(scalartype(t))) | ||||||||||
| tf = similar(t, T) | ||||||||||
| for (c, b) in blocks(t) | ||||||||||
| copy!(block(tf, c), parent($f(Hermitian(b)))) | ||||||||||
| end | ||||||||||
| return tf | ||||||||||
| end | ||||||||||
| end | ||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just use
Strided?