1
1
# Preferences management for storing optimal algorithms in LinearSolve.jl
2
2
3
3
"""
4
- set_algorithm_preferences(categories::Dict{String, String})
4
+ is_always_loaded_algorithm(algorithm_name::String)
5
+
6
+ Determine if an algorithm is always loaded (available without extensions).
7
+ Returns true for algorithms that don't require extensions to be available.
8
+ """
9
+ function is_always_loaded_algorithm (algorithm_name:: String )
10
+ # Algorithms that are always available without requiring extensions
11
+ always_loaded = [
12
+ " LUFactorization" ,
13
+ " GenericLUFactorization" ,
14
+ " MKLLUFactorization" , # Available if MKL is loaded
15
+ " AppleAccelerateLUFactorization" , # Available on macOS
16
+ " SimpleLUFactorization"
17
+ ]
18
+
19
+ return algorithm_name in always_loaded
20
+ end
21
+
22
+ """
23
+ find_best_always_loaded_algorithm(results_df::DataFrame, eltype_str::String, size_range_name::String)
24
+
25
+ Find the best always-loaded algorithm from benchmark results for a specific element type and size range.
26
+ Returns the algorithm name or nothing if no suitable algorithm is found.
27
+ """
28
+ function find_best_always_loaded_algorithm (results_df:: DataFrame , eltype_str:: String , size_range_name:: String )
29
+ # Define size ranges to match the categories
30
+ size_ranges = Dict (
31
+ " tiny (5-20)" => 5 : 20 ,
32
+ " small (20-100)" => 21 : 100 ,
33
+ " medium (100-300)" => 101 : 300 ,
34
+ " large (300-1000)" => 301 : 1000 ,
35
+ " big (1000+)" => 1000 : typemax (Int)
36
+ )
37
+
38
+ size_range = get (size_ranges, size_range_name, nothing )
39
+ if size_range === nothing
40
+ @debug " Unknown size range: $size_range_name "
41
+ return nothing
42
+ end
43
+
44
+ # Filter results for this element type and size range
45
+ filtered_results = filter (row ->
46
+ row. eltype == eltype_str &&
47
+ row. size in size_range &&
48
+ row. success &&
49
+ ! isnan (row. gflops) &&
50
+ is_always_loaded_algorithm (row. algorithm),
51
+ results_df)
52
+
53
+ if nrow (filtered_results) == 0
54
+ return nothing
55
+ end
56
+
57
+ # Calculate average GFLOPs for each always-loaded algorithm
58
+ avg_results = combine (groupby (filtered_results, :algorithm ),
59
+ :gflops => (x -> mean (filter (! isnan, x))) => :avg_gflops )
60
+
61
+ # Sort by performance and return the best
62
+ sort! (avg_results, :avg_gflops , rev= true )
63
+
64
+ if nrow (avg_results) > 0
65
+ return avg_results. algorithm[1 ]
66
+ end
67
+
68
+ return nothing
69
+ end
70
+
71
+ """
72
+ set_algorithm_preferences(categories::Dict{String, String}, results_df::Union{DataFrame, Nothing} = nothing)
5
73
6
74
Set LinearSolve preferences based on the categorized benchmark results.
7
75
These preferences are stored in the main LinearSolve.jl package.
8
76
77
+ This function now supports the dual preference system introduced in LinearSolve.jl v2.31+:
78
+ - `best_algorithm_{type}_{size}`: Overall fastest algorithm
79
+ - `best_always_loaded_{type}_{size}`: Fastest among always-available methods
80
+
9
81
The function handles type fallbacks:
10
82
- If Float32 wasn't benchmarked, uses Float64 results
11
83
- If ComplexF64 wasn't benchmarked, uses ComplexF32 results (if available) or Float64
12
84
- If ComplexF32 wasn't benchmarked, uses Float64 results
13
85
- For complex types, avoids RFLUFactorization due to known issues
86
+
87
+ If results_df is provided, it will be used to determine the best always-loaded algorithm
88
+ from actual benchmark data. Otherwise, a fallback strategy is used.
14
89
"""
15
- function set_algorithm_preferences (categories:: Dict{String, String} )
16
- @info " Setting LinearSolve preferences based on benchmark results..."
90
+ function set_algorithm_preferences (categories:: Dict{String, String} , results_df :: Union{DataFrame, Nothing} = nothing )
91
+ @info " Setting LinearSolve preferences based on benchmark results (dual preference system) ..."
17
92
18
93
# Define the size category names we use
19
94
size_categories = [" tiny" , " small" , " medium" , " large" , " big" ]
@@ -61,19 +136,21 @@ function set_algorithm_preferences(categories::Dict{String, String})
61
136
# Process each target element type and size combination
62
137
for eltype in target_eltypes
63
138
for size_cat in size_categories
64
- # Map size categories to the range strings used in categories
65
- size_range = if size_cat == " tiny"
66
- " 0-128" # Maps to tiny range
67
- elseif size_cat == " small"
68
- " 0-128" # Small also uses this range
69
- elseif size_cat == " medium"
70
- " 128-256" # Medium range
71
- elseif size_cat == " large"
72
- " 256-512" # Large range
73
- elseif size_cat == " big"
74
- " 512+" # Big range
75
- else
76
- continue
139
+ # Find matching size range from benchmarked data for this element type
140
+ size_range = nothing
141
+ if haskey (benchmarked, eltype)
142
+ for range_key in keys (benchmarked[eltype])
143
+ # Check if the range_key contains the size category we're looking for
144
+ # e.g., "medium (100-300)" contains "medium"
145
+ if contains (range_key, size_cat)
146
+ size_range = range_key
147
+ break
148
+ end
149
+ end
150
+ end
151
+
152
+ if size_range === nothing
153
+ continue # No matching size range found for this element type and size category
77
154
end
78
155
79
156
# Determine the algorithm based on fallback rules
@@ -117,11 +194,52 @@ function set_algorithm_preferences(categories::Dict{String, String})
117
194
end
118
195
end
119
196
120
- # Set the preference if we have an algorithm
197
+ # Set preferences if we have an algorithm
121
198
if algorithm != = nothing
122
- pref_key = " best_algorithm_$(eltype) _$(size_cat) "
123
- Preferences. set_preferences! (LinearSolve, pref_key => algorithm; force = true )
124
- @info " Set preference $pref_key = $algorithm in LinearSolve.jl"
199
+ # Set the best overall algorithm preference
200
+ best_pref_key = " best_algorithm_$(eltype) _$(size_cat) "
201
+ Preferences. set_preferences! (LinearSolve, best_pref_key => algorithm; force = true )
202
+ @info " Set preference $best_pref_key = $algorithm in LinearSolve.jl"
203
+
204
+ # Determine the best always-loaded algorithm
205
+ best_always_loaded = nothing
206
+
207
+ # If the best algorithm is already always-loaded, use it
208
+ if is_always_loaded_algorithm (algorithm)
209
+ best_always_loaded = algorithm
210
+ @info " Best algorithm ($algorithm ) is always-loaded for $(eltype) $(size_cat) "
211
+ else
212
+ # Try to find the best always-loaded algorithm from benchmark results
213
+ if results_df != = nothing
214
+ best_always_loaded = find_best_always_loaded_algorithm (results_df, eltype, size_range)
215
+ if best_always_loaded != = nothing
216
+ @info " Found best always-loaded algorithm from benchmarks for $(eltype) $(size_cat) : $best_always_loaded "
217
+ end
218
+ end
219
+
220
+ # Fallback strategy if no benchmark data available or no suitable algorithm found
221
+ if best_always_loaded === nothing
222
+ if eltype == " Float64" || eltype == " Float32"
223
+ # For real types, prefer MKL > LU > Generic
224
+ if mkl_is_best_somewhere
225
+ best_always_loaded = " MKLLUFactorization"
226
+ else
227
+ best_always_loaded = " LUFactorization"
228
+ end
229
+ else
230
+ # For complex types, be more conservative since RFLU has issues
231
+ best_always_loaded = " LUFactorization"
232
+ end
233
+ @info " Using fallback always-loaded algorithm for $(eltype) $(size_cat) : $best_always_loaded "
234
+ end
235
+ end
236
+
237
+ # Set the best always-loaded algorithm preference
238
+ if best_always_loaded != = nothing
239
+ fallback_pref_key = " best_always_loaded_$(eltype) _$(size_cat) "
240
+ Preferences. set_preferences! (LinearSolve, fallback_pref_key => best_always_loaded; force = true )
241
+ @info " Set preference $fallback_pref_key = $best_always_loaded in LinearSolve.jl"
242
+ end
125
243
end
126
244
end
127
245
end
@@ -148,22 +266,33 @@ end
148
266
get_algorithm_preferences()
149
267
150
268
Get the current algorithm preferences from LinearSolve.jl.
151
- Returns preferences organized by element type and size category.
269
+ Returns preferences organized by element type and size category, including both
270
+ best overall and best always-loaded algorithms.
152
271
"""
153
272
function get_algorithm_preferences ()
154
- prefs = Dict {String, String } ()
273
+ prefs = Dict {String, Any } ()
155
274
156
275
# Define the patterns we look for
157
276
target_eltypes = [" Float32" , " Float64" , " ComplexF32" , " ComplexF64" ]
158
277
size_categories = [" tiny" , " small" , " medium" , " large" , " big" ]
159
278
160
279
for eltype in target_eltypes
161
280
for size_cat in size_categories
162
- pref_key = " best_algorithm_$(eltype) _$(size_cat) "
163
- value = Preferences. load_preference (LinearSolve, pref_key, nothing )
164
- if value != = nothing
165
- readable_key = " $(eltype) _$(size_cat) "
166
- prefs[readable_key] = value
281
+ readable_key = " $(eltype) _$(size_cat) "
282
+
283
+ # Get best overall algorithm
284
+ best_pref_key = " best_algorithm_$(eltype) _$(size_cat) "
285
+ best_value = Preferences. load_preference (LinearSolve, best_pref_key, nothing )
286
+
287
+ # Get best always-loaded algorithm
288
+ fallback_pref_key = " best_always_loaded_$(eltype) _$(size_cat) "
289
+ fallback_value = Preferences. load_preference (LinearSolve, fallback_pref_key, nothing )
290
+
291
+ if best_value != = nothing || fallback_value != = nothing
292
+ prefs[readable_key] = Dict (
293
+ " best" => best_value,
294
+ " always_loaded" => fallback_value
295
+ )
167
296
end
168
297
end
169
298
end
@@ -177,18 +306,26 @@ end
177
306
Clear all autotune-related preferences from LinearSolve.jl.
178
307
"""
179
308
function clear_algorithm_preferences ()
180
- @info " Clearing LinearSolve autotune preferences..."
309
+ @info " Clearing LinearSolve autotune preferences (dual preference system) ..."
181
310
182
311
# Define the patterns we look for
183
312
target_eltypes = [" Float32" , " Float64" , " ComplexF32" , " ComplexF64" ]
184
313
size_categories = [" tiny" , " small" , " medium" , " large" , " big" ]
185
314
186
315
for eltype in target_eltypes
187
316
for size_cat in size_categories
188
- pref_key = " best_algorithm_$(eltype) _$(size_cat) "
189
- if Preferences. has_preference (LinearSolve, pref_key)
190
- Preferences. delete_preferences! (LinearSolve, pref_key; force = true )
191
- @info " Cleared preference: $pref_key "
317
+ # Clear best overall algorithm preference
318
+ best_pref_key = " best_algorithm_$(eltype) _$(size_cat) "
319
+ if Preferences. has_preference (LinearSolve, best_pref_key)
320
+ Preferences. delete_preferences! (LinearSolve, best_pref_key; force = true )
321
+ @info " Cleared preference: $best_pref_key "
322
+ end
323
+
324
+ # Clear best always-loaded algorithm preference
325
+ fallback_pref_key = " best_always_loaded_$(eltype) _$(size_cat) "
326
+ if Preferences. has_preference (LinearSolve, fallback_pref_key)
327
+ Preferences. delete_preferences! (LinearSolve, fallback_pref_key; force = true )
328
+ @info " Cleared preference: $fallback_pref_key "
192
329
end
193
330
end
194
331
end
@@ -218,23 +355,32 @@ function show_current_preferences()
218
355
return
219
356
end
220
357
221
- println (" Current LinearSolve.jl autotune preferences:" )
222
- println (" =" ^ 50 )
358
+ println (" Current LinearSolve.jl autotune preferences (dual preference system) :" )
359
+ println (" =" ^ 70 )
223
360
224
361
# Group by element type for better display
225
- by_eltype = Dict {String, Vector{Tuple{String, String}}} ()
226
- for (key, algorithm ) in prefs
362
+ by_eltype = Dict{String, Vector{Tuple{String, Dict{ String, Any} }}}()
363
+ for (key, pref_dict ) in prefs
227
364
eltype, size_cat = split (key, " _" , limit= 2 )
228
365
if ! haskey (by_eltype, eltype)
229
- by_eltype[eltype] = Vector {Tuple{String, String}} ()
366
+ by_eltype[eltype] = Vector {Tuple{String, Dict{ String, Any} }} ()
230
367
end
231
- push! (by_eltype[eltype], (size_cat, algorithm ))
368
+ push! (by_eltype[eltype], (size_cat, pref_dict ))
232
369
end
233
370
234
371
for eltype in sort (collect (keys (by_eltype)))
235
372
println (" \n $eltype :" )
236
- for (size_cat, algorithm) in sort (by_eltype[eltype])
237
- println (" $size_cat : $algorithm " )
373
+ for (size_cat, pref_dict) in sort (by_eltype[eltype])
374
+ println (" $size_cat :" )
375
+ best_alg = get (pref_dict, " best" , nothing )
376
+ always_loaded_alg = get (pref_dict, " always_loaded" , nothing )
377
+
378
+ if best_alg != = nothing
379
+ println (" Best overall: $best_alg " )
380
+ end
381
+ if always_loaded_alg != = nothing
382
+ println (" Best always-loaded: $always_loaded_alg " )
383
+ end
238
384
end
239
385
end
240
386
@@ -246,4 +392,7 @@ function show_current_preferences()
246
392
247
393
timestamp = Preferences. load_preference (LinearSolve, " autotune_timestamp" , " unknown" )
248
394
println (" \n Last updated: $timestamp " )
395
+ println (" \n NOTE: This uses the enhanced dual preference system where LinearSolve.jl" )
396
+ println (" will try the best overall algorithm first, then fall back to the best" )
397
+ println (" always-loaded algorithm if extensions are not available." )
249
398
end
0 commit comments