Skip to content

Commit 37b96cb

Browse files
lkdvosclaude
andauthored
Add a TBLIS.jl package extension (#290)
* Add a TBLIS.jl package extension Adds `TBLISBackend`, which routes `tensoradd!`, `tensortrace!` and `tensorcontract!` through the TBLIS library via TBLIS.jl. TBLIS contracts strided tensors in place, so it avoids the permuted intermediates that the BLAS-based backend has to materialize. The backend is opt-in: loading TBLIS.jl does not register a `select_backend` method. Arguments TBLIS cannot express, that is mixed or unsupported element types, non-strided arrays and conjugated outputs, throw an `ArgumentError` rather than being handed to another backend, so that a contraction which never reaches TBLIS cannot pass for one that did. Conjugation is carried as an ordinary runtime flag on the `TBLISTensor` descriptor rather than in the type of a `StridedView`, which keeps the entry points type stable without branching over `conj` variants. A conjugation already present on the input, as for an `Adjoint`, combines with the requested one. Two library quirks are worked around: * `tblis_tensor_mult` ignores the per-tensor conjugation flags, unlike `tblis_tensor_add`. When both factors are conjugated this is resolved by conjugating the output in place, and otherwise by materializing the conjugated factor into a temporary from the allocator. * TBLIS.jl only exposes its tensor constructor for `StridedArray` and offers no way to set the conjugation flag, so the descriptor is initialized through the low-level bindings and every referenced buffer is rooted with `GC.@preserve`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Address review: drop the descriptor struct and flatten the call paths * Replace the `TBLISTensor` struct with a `tblis_tensor` function returning the `Ref{tblis_tensor}` directly. The caller owns the length and stride buffers and keeps them rooted, so nothing has to be wrapped to stay alive. * `isconj` specializes on `StridedView` to fold any conjugation the view already carries into the one the caller requested. * Inline `tblis_add!` and `unsafe_add!` into `tensoradd!` and `tensortrace!`. `materialize_conj` now goes through `tensoradd!` instead of a private copy of the same call sequence. * Drop the `try`/`finally` around the conjugated temporaries and free them on the success path only, matching how the cuTENSOR extension handles this. * Guard the threading test so it only asks for as many threads as the machine reports, for single-core CI runners. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Address review: document the conjugated-output case, trim comments * Note at each `SV` conversion that it is there to support inputs such as `Adjoint`, which have no `strides` or `pointer` of their own. * Explain why the `C` descriptor is always built unconjugated: TBLIS applies the flag of `C` when reading the `β * C` term but not when writing the result back, so a conjugated output would conjugate half the operation. `check_arguments` rejects such a `C` for that reason. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Drop the explanatory comments from the extension Keeps the section banners and the `tblis_tensor` docstring. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Note the two TBLIS conjugation restrictions and make the errors lazy One-line comments for the two library restrictions that are not visible from the code, and `LazyString` messages for the three argument-check errors. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Reject the TBLIS backend on Windows and fix the 1.12 constructor warning The Windows build of tblis_jll aborts the process with "posix_memalign: Invalid argument" out of tblis::MemoryPool::acquire as soon as a contraction reaches the GEMM kernels. The abort does not tear the process down cleanly either, so CI hung until the 6h runner timeout instead of failing. Reject the backend up front on Windows with an ArgumentError that points at StridedBLAS, skip the TBLIS test file there, and document the restriction. Rename the tblis_tensor helper to tblis_tensor_ref: tblis_tensor is a struct in TBLIS, so the method was extending its constructor through an implicit `using` binding, which Julia 1.12 warns about on load. Also cap the CI jobs at 90 minutes so that a native library that aborts or deadlocks fails the job instead of wedging a runner for six hours. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * code review --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 620620c commit 37b96cb

7 files changed

Lines changed: 556 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ jobs:
1717
test:
1818
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}
1919
runs-on: ${{ matrix.os }}
20+
timeout-minutes: 90
2021
strategy:
2122
fail-fast: false
2223
matrix:
@@ -49,6 +50,7 @@ jobs:
4950
needs: test
5051
name: Julia nightly - ${{ matrix.os }} - ${{ matrix.arch }}
5152
runs-on: ${{ matrix.os }}
53+
timeout-minutes: 90
5254
strategy:
5355
fail-fast: false
5456
matrix:

Project.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9"
2626
GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7"
2727
JLArrays = "27aeb0d3-9eb9-45fb-866b-73c2ecf80fcb"
2828
Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6"
29+
TBLIS = "48530278-0828-4a49-9772-0f3830dfa1e9"
2930

3031
[extensions]
3132
TensorOperationsAMDGPUExt = "AMDGPU"
3233
TensorOperationsBumperExt = "Bumper"
3334
TensorOperationsChainRulesCoreExt = "ChainRulesCore"
3435
TensorOperationsMooncakeExt = "Mooncake"
36+
TensorOperationsTBLISExt = "TBLIS"
3537
TensorOperationsCUDACoreExt = "CUDACore"
3638
TensorOperationsEnzymeExt = "Enzyme"
3739
TensorOperationsGPUArraysExt = "GPUArrays"
@@ -62,6 +64,7 @@ PtrArrays = "1.2"
6264
Random = "1"
6365
Strided = "2.6"
6466
StridedViews = "0.5"
67+
TBLIS = "0.3"
6568
Test = "1"
6669
TupleTools = "1.6"
6770
VectorInterface = "0.4.1, 0.5, 0.6"
@@ -84,8 +87,9 @@ JLArrays = "27aeb0d3-9eb9-45fb-866b-73c2ecf80fcb"
8487
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
8588
Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6"
8689
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
90+
TBLIS = "48530278-0828-4a49-9772-0f3830dfa1e9"
8791
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
8892
cuTENSOR = "011b41b2-24ef-40a8-b3eb-fa098493e9e1"
8993

9094
[targets]
91-
test = ["Test", "Random", "DynamicPolynomials", "ChainRulesTestUtils", "ChainRulesCore", "cuRAND", "CUDACore", "cuTENSOR", "Aqua", "Logging", "Bumper", "Mooncake", "Enzyme", "EnzymeTestUtils", "Adapt", "JLArrays", "AMDGPU"]
95+
test = ["Test", "Random", "DynamicPolynomials", "ChainRulesTestUtils", "ChainRulesCore", "cuRAND", "CUDACore", "cuTENSOR", "Aqua", "Logging", "Bumper", "Mooncake", "Enzyme", "EnzymeTestUtils", "Adapt", "JLArrays", "AMDGPU", "TBLIS"]

docs/src/man/backends.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ TensorOperations.BaseCopy
6464
TensorOperations.BaseView
6565
TensorOperations.StridedNative
6666
TensorOperations.StridedBLAS
67+
TensorOperations.TBLISBackend
6768
TensorOperations.cuTENSORBackend
6869
```
6970

@@ -74,6 +75,29 @@ On the other hand, the `BaseCopy` and `BaseView` backends are used for arrays th
7475
These are designed to be as general as possible, and as a result are not as performant as specific implementations.
7576
Nevertheless, they can be useful for debugging purposes or for working with custom tensor types that have limited support for methods outside of `Base`.
7677

78+
The `TBLISBackend` routes the primitive operations through the [TBLIS](https://github.com/devinamatthews/tblis) library.
79+
TBLIS contracts strided tensors in place instead of reshaping them into matrices, and can therefore avoid the intermediate permuted copies that `StridedBLAS` sometimes has to allocate.
80+
It is opt-in, in the sense that loading `TBLIS.jl` does not change the default backend selection, and it is only available through a package extension for [`TBLIS.jl`](https://github.com/QuantumKitHub/TBLIS.jl):
81+
82+
```julia
83+
using TensorOperations, TBLIS
84+
TBLIS.set_num_threads(8)
85+
@tensor backend = TensorOperations.TBLISBackend() D[a, b, c, d] := A[a, e, c, f] * B[g, d, e] * conj(C[g, f, b])
86+
```
87+
88+
TBLIS requires all tensors in a single operation to share one element type out of `Float32`, `Float64`, `ComplexF32` and `ComplexF64`.
89+
Arguments that do not satisfy this, as well as non-strided arrays, are rejected with an `ArgumentError` instead of being passed on to another backend, so that a contraction which cannot actually reach TBLIS is not silently run somewhere else.
90+
91+
The backend is not supported on Windows.
92+
`tblis_jll` does ship a Windows build, but it aborts the process from inside the library as soon as a contraction reaches its GEMM kernels, so calls there are rejected with an `ArgumentError` instead.
93+
94+
Note that contracting in place trades throughput for memory rather than being a free win.
95+
With BLAS, TBLIS and `Strided.jl` all given the same number of threads, this backend is roughly on par with `StridedBLAS` for permuted real contractions and slower for other shapes, while allocating no permuted temporaries at all.
96+
97+
!!! warning
98+
As of `tblis_jll` v1.3, TBLIS has no competitive support for complex element types.
99+
Its complex contraction kernels run an order of magnitude slower than the corresponding BLAS calls, so `StridedBLAS` is the better choice for complex-valued contractions.
100+
77101
Finally, we also provide a `cuTENSORBackend` for use with the `cuTENSOR.jl` library, which is a NVidia GPU-accelerated tensor contraction library.
78102
This backend is only available through a package extension for `cuTENSOR`.
79103

@@ -89,7 +113,7 @@ Users can also define their own backends, to facilitate experimentation with new
89113
This can be done by defining a new type that is a subtype of `AbstractBackend`, and dispatching on this type in the implementation of the primitive tensor operations.
90114
In particular, the only required implemented methods are [`tensoradd!`](@ref), [`tensortrace!`](@ref), [`tensorcontract!`](@ref).
91115

92-
For example, [`TensorOperationsTBLIS`](https://github.com/lkdvos/TensorOperationsTBLIS.jl) is a wrapper that provides a backend for tensor contractions using the [TBLIS](https://github.com/devinamatthews/tblis) library.
116+
For example, the `TBLISBackend` above is implemented in exactly this way, as a package extension that only adds methods for these three functions.
93117

94118
## Allocators
95119

ext/TensorOperationsTBLISExt.jl

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
module TensorOperationsTBLISExt
2+
3+
using TensorOperations
4+
using TensorOperations: TensorOperations as TO
5+
using TensorOperations: TBLISBackend, DefaultAllocator, Index2Tuple
6+
using TensorOperations: StridedView, isstrided
7+
using TensorOperations: argcheck_tensoradd, dimcheck_tensoradd,
8+
argcheck_tensortrace, dimcheck_tensortrace,
9+
argcheck_tensorcontract, dimcheck_tensorcontract
10+
using TensorOperations: add_labels, trace_labels, contract_labels
11+
using TensorOperations: tensoralloc_add, tensorfree!
12+
13+
using TBLIS
14+
using TBLIS: len_type, stride_type, tblis_tensor
15+
16+
using LinearAlgebra.BLAS: BlasFloat
17+
const SV = StridedView
18+
19+
const TBLISFloat = BlasFloat
20+
21+
# `tblis_jll` does ship a Windows build, but it aborts the process with
22+
# "posix_memalign: Invalid argument" from `tblis::MemoryPool::acquire` as soon as a
23+
# contraction reaches the GEMM kernels, so nothing is handed to the library there.
24+
const PLATFORM_SUPPORTED = !Sys.iswindows()
25+
26+
#-------------------------------------------------------------------------------------------
27+
# Wrapping Julia arrays as TBLIS tensors
28+
#-------------------------------------------------------------------------------------------
29+
for (T, init) in (
30+
(:Float32, :tblis_init_tensor_scaled_s),
31+
(:Float64, :tblis_init_tensor_scaled_d),
32+
(:ComplexF32, :tblis_init_tensor_scaled_c),
33+
(:ComplexF64, :tblis_init_tensor_scaled_z),
34+
)
35+
@eval function init_tensor!(
36+
p::Ptr{tblis_tensor}, A::StridedView{$T, N}, α::$T,
37+
len::Vector{len_type}, stride::Vector{stride_type}
38+
) where {N}
39+
return TBLIS.$init(p, α, Cuint(N), pointer(len), pointer(A), pointer(stride))
40+
end
41+
end
42+
43+
isconj(A::StridedView{T}, conjA::Bool) where {T} = T <: Complex && (conjA (A.op === conj))
44+
tblis_dims(A::StridedView) = (collect(len_type, size(A)), collect(stride_type, strides(A)))
45+
46+
"""
47+
tblis_tensor_ref(A::StridedView, α, len, stride, conj) -> Ref{TBLIS.tblis_tensor}
48+
49+
Descriptor for `α * A`, conjugated when `conj` is set, using `len` and `stride` as the
50+
buffers handed to TBLIS.
51+
52+
The descriptor only stores raw pointers into `A`, `len` and `stride`, so all three, along
53+
with the returned `Ref`, have to be kept alive by the caller for as long as TBLIS may access
54+
them. Note that `conj` is the *total* conjugation applied to the data of `A`, as computed by
55+
[`isconj`](@ref), not the flag the caller was handed.
56+
"""
57+
function tblis_tensor_ref(
58+
A::StridedView{T, N}, α::T,
59+
len::Vector{len_type}, stride::Vector{stride_type}, conj::Bool
60+
) where {T <: TBLISFloat, N}
61+
ref = Ref{tblis_tensor}()
62+
GC.@preserve A len stride ref begin
63+
p = Base.unsafe_convert(Ptr{tblis_tensor}, ref)
64+
init_tensor!(p, A, α, len, stride)
65+
conj && setproperty!(p, :conj, Cint(1))
66+
end
67+
return ref
68+
end
69+
70+
labels(ein::Tuple{Vararg{Char}}) = String(UInt8[c for c in ein])
71+
72+
#-------------------------------------------------------------------------------------------
73+
# Argument checking
74+
#-------------------------------------------------------------------------------------------
75+
@noinline function throw_unsupported_platform(f)
76+
return throw(
77+
ArgumentError(
78+
LazyString(
79+
"TBLISBackend is not supported on ", Base.BUILD_TRIPLET, ": the tblis_jll ",
80+
"binaries for this platform abort the process from inside the library. ",
81+
"Use another backend, such as StridedBLAS(), for ", f
82+
)
83+
)
84+
)
85+
end
86+
87+
@noinline function throw_eltype(f, tensors)
88+
return throw(
89+
ArgumentError(
90+
LazyString(
91+
"TBLISBackend requires all tensors of ", f, " to share a single element ",
92+
"type out of Float32, Float64, ComplexF32 and ComplexF64, got ",
93+
join(eltype.(tensors), ", ")
94+
)
95+
)
96+
)
97+
end
98+
99+
@noinline function throw_strided(f, tensors)
100+
types = join(typeof.(tensors), ", ")
101+
return throw(ArgumentError(lazy"TBLISBackend requires strided arrays for $f, got $types"))
102+
end
103+
104+
@noinline throw_conj_output(f) = throw(
105+
ArgumentError(lazy"TBLISBackend cannot write into a conjugated view in $f")
106+
)
107+
108+
function check_arguments(f, C::AbstractArray, As::AbstractArray...)
109+
PLATFORM_SUPPORTED || throw_unsupported_platform(f)
110+
tensors = (C, As...)
111+
T = eltype(C)
112+
(T <: TBLISFloat && all(A -> eltype(A) === T, As)) || throw_eltype(f, tensors)
113+
all(isstrided, tensors) || throw_strided(f, tensors)
114+
# `tblis_tensor_add` applies the flag of `C` when reading `β * C` but not when writing back
115+
isconj(SV(C), false) && throw_conj_output(f)
116+
return nothing
117+
end
118+
119+
#-------------------------------------------------------------------------------------------
120+
# Operations
121+
#-------------------------------------------------------------------------------------------
122+
function TO.tensoradd!(
123+
C::AbstractArray,
124+
A::AbstractArray, pA::Index2Tuple, conjA::Bool,
125+
α::Number, β::Number,
126+
backend::TBLISBackend, allocator = DefaultAllocator()
127+
)
128+
check_arguments(TO.tensoradd!, C, A)
129+
argcheck_tensoradd(C, A, pA)
130+
dimcheck_tensoradd(C, A, pA)
131+
Base.mightalias(C, A) &&
132+
throw(ArgumentError("output tensor must not be aliased with input tensor"))
133+
134+
T = eltype(C)
135+
einA, einC = add_labels(pA)
136+
Av, Cv = SV(A), SV(C)
137+
lenA, strideA = tblis_dims(Av)
138+
lenC, strideC = tblis_dims(Cv)
139+
GC.@preserve Av Cv lenA strideA lenC strideC begin
140+
tA = tblis_tensor_ref(Av, convert(T, α), lenA, strideA, isconj(Av, conjA))
141+
tC = tblis_tensor_ref(Cv, convert(T, β), lenC, strideC, false)
142+
TBLIS.tblis_tensor_add(C_NULL, C_NULL, tA, labels(einA), tC, labels(einC))
143+
end
144+
return C
145+
end
146+
147+
function TO.tensortrace!(
148+
C::AbstractArray,
149+
A::AbstractArray, p::Index2Tuple, q::Index2Tuple, conjA::Bool,
150+
α::Number, β::Number,
151+
backend::TBLISBackend, allocator = DefaultAllocator()
152+
)
153+
check_arguments(TO.tensortrace!, C, A)
154+
argcheck_tensortrace(C, A, p, q)
155+
dimcheck_tensortrace(C, A, p, q)
156+
Base.mightalias(C, A) &&
157+
throw(ArgumentError("output tensor must not be aliased with input tensor"))
158+
159+
T = eltype(C)
160+
einA, einC = trace_labels(p, q)
161+
Av, Cv = SV(A), SV(C)
162+
lenA, strideA = tblis_dims(Av)
163+
lenC, strideC = tblis_dims(Cv)
164+
GC.@preserve Av Cv lenA strideA lenC strideC begin
165+
tA = tblis_tensor_ref(Av, convert(T, α), lenA, strideA, isconj(Av, conjA))
166+
tC = tblis_tensor_ref(Cv, convert(T, β), lenC, strideC, false)
167+
TBLIS.tblis_tensor_add(C_NULL, C_NULL, tA, labels(einA), tC, labels(einC))
168+
end
169+
return C
170+
end
171+
172+
function TO.tensorcontract!(
173+
C::AbstractArray,
174+
A::AbstractArray, pA::Index2Tuple, conjA::Bool,
175+
B::AbstractArray, pB::Index2Tuple, conjB::Bool,
176+
pAB::Index2Tuple,
177+
α::Number, β::Number,
178+
backend::TBLISBackend, allocator = DefaultAllocator()
179+
)
180+
check_arguments(TO.tensorcontract!, C, A, B)
181+
argcheck_tensorcontract(C, A, pA, B, pB, pAB)
182+
dimcheck_tensorcontract(C, A, pA, B, pB, pAB)
183+
(Base.mightalias(C, A) || Base.mightalias(C, B)) &&
184+
throw(ArgumentError("output tensor must not be aliased with input tensor"))
185+
186+
T = eltype(C)
187+
einA, einB, einC = contract_labels(pA, pB, pAB)
188+
α′ = convert(T, α)
189+
β′ = convert(T, β)
190+
Av, Bv, Cv = SV(A), SV(B), SV(C)
191+
isconjA = isconj(Av, conjA)
192+
isconjB = isconj(Bv, conjB)
193+
194+
# `tblis_tensor_mult` ignores the conjugation flags, so resolve them into the data first
195+
if isconjA && isconjB
196+
iszero(β′) || conj!(Cv)
197+
tblis_mult!(Cv, Av, Bv, einA, einB, einC, conj(α′), conj(β′))
198+
conj!(Cv)
199+
elseif isconjA
200+
A′ = materialize_conj(Av, conjA, α′, allocator)
201+
tblis_mult!(Cv, SV(A′), Bv, einA, einB, einC, one(T), β′)
202+
tensorfree!(A′, allocator)
203+
elseif isconjB
204+
B′ = materialize_conj(Bv, conjB, one(T), allocator)
205+
tblis_mult!(Cv, Av, SV(B′), einA, einB, einC, α′, β′)
206+
tensorfree!(B′, allocator)
207+
else
208+
tblis_mult!(Cv, Av, Bv, einA, einB, einC, α′, β′)
209+
end
210+
return C
211+
end
212+
213+
function tblis_mult!(
214+
C::StridedView{T}, A::StridedView{T}, B::StridedView{T},
215+
einA, einB, einC, α::T, β::T
216+
) where {T <: TBLISFloat}
217+
lenA, strideA = tblis_dims(A)
218+
lenB, strideB = tblis_dims(B)
219+
lenC, strideC = tblis_dims(C)
220+
GC.@preserve A B C lenA strideA lenB strideB lenC strideC begin
221+
tA = tblis_tensor_ref(A, α, lenA, strideA, false)
222+
tB = tblis_tensor_ref(B, one(T), lenB, strideB, false)
223+
tC = tblis_tensor_ref(C, β, lenC, strideC, false)
224+
TBLIS.tblis_tensor_mult(
225+
C_NULL, C_NULL, tA, labels(einA), tB, labels(einB), tC, labels(einC)
226+
)
227+
end
228+
return C
229+
end
230+
231+
function materialize_conj(
232+
A::StridedView{T, N}, conjA::Bool, α::T, allocator
233+
) where {T <: TBLISFloat, N}
234+
pA = (ntuple(identity, N), ())
235+
A′ = tensoralloc_add(T, A, pA, false, Val(true), allocator)
236+
TO.tensoradd!(A′, A, pA, conjA, α, zero(T), TBLISBackend(), allocator)
237+
return A′
238+
end
239+
240+
end # module TensorOperationsTBLISExt

src/backends.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,28 @@ struct StridedBLAS <: AbstractBackend end
7171

7272
const StridedBackend = Union{StridedNative, StridedBLAS}
7373

74+
# TBLIS backend
75+
#--------------
76+
"""
77+
TBLISBackend()
78+
79+
Backend for tensor operations on strided arrays that is based on the [TBLIS](https://github.com/devinamatthews/tblis) library.
80+
TBLIS performs tensor additions, traces and contractions directly on strided memory, without the transpositions and temporaries that a BLAS-based approach requires.
81+
This backend is only available through a package extension for [TBLIS.jl](https://github.com/QuantumKitHub/TBLIS.jl).
82+
83+
TBLIS requires all tensors in a single operation to be of the same element type, and only supports `BLASFloat`s.
84+
85+
!!! warning
86+
This backend is currently not supported on Windows: the `tblis_jll` binaries for that platform
87+
abort the process from inside the library, so calls are rejected with an `ArgumentError`.
88+
89+
!!! warning
90+
As of `tblis_jll` v1.3, TBLIS has no competitive support for complex element types: its
91+
complex contraction kernels run an order of magnitude slower than the corresponding BLAS calls.
92+
Prefer [`StridedBLAS`](@ref TensorOperations.StridedBLAS) for complex-valued contractions.
93+
"""
94+
struct TBLISBackend <: AbstractBackend end
95+
7496
# CuTENSOR backend
7597
#-----------------
7698
"""

test/runtests.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,23 @@ if !is_buildkite
8181
include("butensor.jl")
8282
end
8383

84+
if Sys.iswindows()
85+
# the Windows build of `tblis_jll` aborts the process from inside the library, so
86+
# the extension refuses to call into it and there is nothing else to test
87+
using TBLIS
88+
@testset "TBLIS extension (unsupported platform)" begin
89+
A = randn(Float64, (3, 4))
90+
@test_throws ArgumentError tensoradd!(
91+
zeros(Float64, (4, 3)), A, ((2, 1), ()), false, 1, 0,
92+
TensorOperations.TBLISBackend()
93+
)
94+
end
95+
else
96+
@testset "TBLIS extension" verbose = true begin
97+
include("tblis.jl")
98+
end
99+
end
100+
84101
@testset "Polynomials" begin
85102
include("polynomials.jl")
86103
end

0 commit comments

Comments
 (0)