Skip to content

Commit cdbc39d

Browse files
authored
Merge pull request #169 from JuliaLinearAlgebra/im/accelerate
Add threading support for Apple Accelerate
2 parents 072b5f6 + 006da93 commit cdbc39d

File tree

3 files changed

+151
-0
lines changed

3 files changed

+151
-0
lines changed

src/threading.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@
3131
} MKLVersion;
3232

3333

34+
/* Apple Accelerate doesn't allow setting the number of threads directly, it only has an
35+
* option to do single-threaded or multi-threaded. That is controlled via the BLASSetThreading
36+
* API introduced in macOS 15.
37+
*
38+
* These constants are from the vecLib.framework/Headers/thread_api.h file
39+
*/
40+
#define ACCELERATE_BLAS_THREADING_MULTI_THREADED 0
41+
#define ACCELERATE_BLAS_THREADING_SINGLE_THREADED 1
42+
3443
/*
3544
* We provide a flexible thread getter/setter interface here; by calling `lbt_set_num_threads()`
3645
* libblastrampoline will propagate the call through to its loaded libraries as long as the
@@ -50,6 +59,7 @@ static char * getter_names[MAX_THREADING_NAMES] = {
5059
"nvpl_lapack_get_max_threads",
5160
// We special-case MKL in the lookup loop below
5261
//"MKL_Domain_Get_Max_Threads",
62+
// We special-case Apple Accelerate below
5363
NULL
5464
};
5565

@@ -60,6 +70,7 @@ static char * setter_names[MAX_THREADING_NAMES] = {
6070
"nvpl_lapack_set_num_threads",
6171
// We special-case MKL in the lookup loop below
6272
//"MKL_Domain_Set_Num_Threads",
73+
// We special-case Apple Accelerate below
6374
NULL
6475
};
6576

@@ -129,6 +140,37 @@ LBT_DLLEXPORT int32_t lbt_get_num_threads() {
129140
}
130141
}
131142
}
143+
144+
// Special case Apple Accelerate because we have to determine if we are single-threaded or multi-threaded
145+
// This API only exists on macOS 15+.
146+
int (*fptr_acc)(void) = lookup_symbol(lib->handle, "BLASGetThreading");
147+
if (fptr_acc != NULL) {
148+
int nthreads = fptr_acc();
149+
150+
if(nthreads == ACCELERATE_BLAS_THREADING_MULTI_THREADED) {
151+
int (*fptr_acc_nthreads)(void) = lookup_symbol(lib->handle, "APPLE_NTHREADS");
152+
if (fptr_acc != NULL) {
153+
// In Accelerate, there is a symbol called APPLE_NTHREADS, which appears to be a function we
154+
// can call to get an integer saying the number of CPU threads. There is no documentation for this
155+
// anywhere accessible online, but testing two different CPUs seem to suggest it is CPU cores.
156+
//
157+
// Doing this:
158+
// julia> @ccall AppleAccelerate.libacc.APPLE_NTHREADS()::Int
159+
//
160+
// The M2 Max returned 12, M4 Max returned 16, which is the total number of cores (both big and little)
161+
// in each processor.
162+
int nthreads = fptr_acc_nthreads();
163+
max_threads = max(max_threads, nthreads);
164+
} else {
165+
// This number is arbitrary because we have no idea how many threads are actually in use,
166+
// but greater than 1 to mean multi-threaded.
167+
max_threads = max(max_threads, 2);
168+
}
169+
} else {
170+
// Single-threaded
171+
max_threads = max(max_threads, 1);
172+
}
173+
}
132174
}
133175
return max_threads;
134176
}
@@ -157,5 +199,16 @@ LBT_DLLEXPORT void lbt_set_num_threads(int32_t nthreads) {
157199
fptr(nthreads, MKL_DOMAIN_BLAS);
158200
fptr(nthreads, MKL_DOMAIN_LAPACK);
159201
}
202+
203+
// Special case Apple Accelerate because we have to determine if we must set multi-threaded or single-threaded
204+
// This API only exists on macOS 15+.
205+
int (*fptr_acc)(int) = lookup_symbol(lib->handle, "BLASSetThreading");
206+
if (fptr_acc != NULL) {
207+
if(nthreads > 1) {
208+
fptr_acc(ACCELERATE_BLAS_THREADING_MULTI_THREADED);
209+
} else {
210+
fptr_acc(ACCELERATE_BLAS_THREADING_SINGLE_THREADED);
211+
}
212+
}
160213
}
161214
}

test/accelerate.jl

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
using Libdl, Test
2+
3+
# Taken from AppleAccelerate.jl to avoid a dependency on it
4+
const libacc = "/System/Library/Frameworks/Accelerate.framework/Accelerate"
5+
const libacc_info_plist = "/System/Library/Frameworks/Accelerate.framework/Versions/Current/Resources/Info.plist"
6+
7+
function get_macos_version(normalize=true)
8+
@static if !Sys.isapple()
9+
return nothing
10+
end
11+
12+
plist_lines = split(String(read("/System/Library/CoreServices/SystemVersion.plist")), "\n")
13+
vers_idx = findfirst(l -> occursin("ProductVersion", l), plist_lines)
14+
if vers_idx === nothing
15+
return nothing
16+
end
17+
18+
m = match(r">([\d\.]+)<", plist_lines[vers_idx+1])
19+
if m === nothing
20+
return nothing
21+
end
22+
23+
ver = VersionNumber(only(m.captures))
24+
if normalize && ver.major == 16
25+
return VersionNumber(26, ver.minor, ver.patch)
26+
end
27+
return ver
28+
end
29+
30+
31+
# Load the Accelerate library
32+
libacc_handle = dlopen(libacc)
33+
@testset "Accelerate ILP64 loading" begin
34+
# ILP64 requires macOS 13.3+
35+
if get_macos_version() >= v"13.3"
36+
# Load the ILP64 interface
37+
lbt_forward(lbt_handle, libacc; clear=true, suffix_hint="\x1a\$NEWLAPACK\$ILP64")
38+
39+
# Test that we have only one library loaded
40+
config = lbt_get_config(lbt_handle)
41+
libs = unpack_loaded_libraries(config)
42+
@test length(libs) == 1
43+
44+
# Test that it's Accelerate and it's correctly identified
45+
@test libs[1].libname == libacc
46+
@test libs[1].interface == LBT_INTERFACE_ILP64
47+
48+
# Test that `dgemm` forwards to `dgemm_` within the Accelerate library
49+
acc_dgemm = dlsym(libacc_handle, "dgemm\$NEWLAPACK\$ILP64")
50+
@test lbt_get_forward(lbt_handle, "dgemm_", LBT_INTERFACE_ILP64) == acc_dgemm
51+
end
52+
end
53+
54+
@testset "Accelerate LP64 loading" begin
55+
# New LAPACK interface requires macOS 13.3+
56+
if get_macos_version() >= v"13.3"
57+
# Load the LP64 interface
58+
lbt_forward(lbt_handle, libacc; clear=true, suffix_hint="\x1a\$NEWLAPACK")
59+
60+
# Test that we have only one library loaded
61+
config = lbt_get_config(lbt_handle)
62+
libs = unpack_loaded_libraries(config)
63+
@test length(libs) == 1
64+
65+
# Test that it's Accelerate and it's correctly identified
66+
@test libs[1].libname == libacc
67+
@test libs[1].interface == LBT_INTERFACE_LP64
68+
69+
# Test that `dgemm` forwards to `dgemm_` within the Accelerate library
70+
acc_dgemm = dlsym(libacc_handle, "dgemm\$NEWLAPACK")
71+
@test lbt_get_forward(lbt_handle, "dgemm_", LBT_INTERFACE_LP64) == acc_dgemm
72+
end
73+
end
74+
75+
@testset "Accelerate threading" begin
76+
# This threading API will only work on v15 and above
77+
if get_macos_version() >= v"15"
78+
lbt_forward(lbt_handle, libacc; clear=true)
79+
80+
# Set to single-threaded
81+
lbt_set_num_threads(lbt_handle, 1)
82+
@test lbt_get_num_threads(lbt_handle) == 1
83+
84+
# Set to multi-threaded
85+
# Accelerate doesn't actually let us say how many threads, so we must test for greater than
86+
lbt_set_num_threads(lbt_handle, 2)
87+
@test lbt_get_num_threads(lbt_handle) > 1
88+
89+
# Set back to single-threaded
90+
lbt_set_num_threads(lbt_handle, 1)
91+
@test lbt_get_num_threads(lbt_handle) == 1
92+
end
93+
end

test/runtests.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,3 +255,8 @@ end
255255

256256
# Run our "direct" tests within Julia
257257
include("direct.jl")
258+
259+
# Run some Apple Accelerate tests, but only on Apple
260+
@static if Sys.isapple()
261+
include("accelerate.jl")
262+
end

0 commit comments

Comments
 (0)