11# Preferences management for storing optimal algorithms in LinearSolve.jl
22
33"""
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)
573
674Set LinearSolve preferences based on the categorized benchmark results.
775These preferences are stored in the main LinearSolve.jl package.
876
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+
981The function handles type fallbacks:
1082- If Float32 wasn't benchmarked, uses Float64 results
1183- If ComplexF64 wasn't benchmarked, uses ComplexF32 results (if available) or Float64
1284- If ComplexF32 wasn't benchmarked, uses Float64 results
1385- 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.
1489"""
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) ..."
1792
1893 # Define the size category names we use
1994 size_categories = [" tiny" , " small" , " medium" , " large" , " big" ]
@@ -61,19 +136,21 @@ function set_algorithm_preferences(categories::Dict{String, String})
61136 # Process each target element type and size combination
62137 for eltype in target_eltypes
63138 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
77154 end
78155
79156 # Determine the algorithm based on fallback rules
@@ -117,11 +194,52 @@ function set_algorithm_preferences(categories::Dict{String, String})
117194 end
118195 end
119196
120- # Set the preference if we have an algorithm
197+ # Set preferences if we have an algorithm
121198 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
125243 end
126244 end
127245 end
@@ -148,22 +266,33 @@ end
148266 get_algorithm_preferences()
149267
150268Get 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.
152271"""
153272function get_algorithm_preferences ()
154- prefs = Dict {String, String } ()
273+ prefs = Dict {String, Any } ()
155274
156275 # Define the patterns we look for
157276 target_eltypes = [" Float32" , " Float64" , " ComplexF32" , " ComplexF64" ]
158277 size_categories = [" tiny" , " small" , " medium" , " large" , " big" ]
159278
160279 for eltype in target_eltypes
161280 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+ )
167296 end
168297 end
169298 end
@@ -177,18 +306,26 @@ end
177306Clear all autotune-related preferences from LinearSolve.jl.
178307"""
179308function clear_algorithm_preferences ()
180- @info " Clearing LinearSolve autotune preferences..."
309+ @info " Clearing LinearSolve autotune preferences (dual preference system) ..."
181310
182311 # Define the patterns we look for
183312 target_eltypes = [" Float32" , " Float64" , " ComplexF32" , " ComplexF64" ]
184313 size_categories = [" tiny" , " small" , " medium" , " large" , " big" ]
185314
186315 for eltype in target_eltypes
187316 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 "
192329 end
193330 end
194331 end
@@ -218,23 +355,32 @@ function show_current_preferences()
218355 return
219356 end
220357
221- println (" Current LinearSolve.jl autotune preferences:" )
222- println (" =" ^ 50 )
358+ println (" Current LinearSolve.jl autotune preferences (dual preference system) :" )
359+ println (" =" ^ 70 )
223360
224361 # 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
227364 eltype, size_cat = split (key, " _" , limit= 2 )
228365 if ! haskey (by_eltype, eltype)
229- by_eltype[eltype] = Vector {Tuple{String, String}} ()
366+ by_eltype[eltype] = Vector {Tuple{String, Dict{ String, Any} }} ()
230367 end
231- push! (by_eltype[eltype], (size_cat, algorithm ))
368+ push! (by_eltype[eltype], (size_cat, pref_dict ))
232369 end
233370
234371 for eltype in sort (collect (keys (by_eltype)))
235372 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
238384 end
239385 end
240386
@@ -246,4 +392,7 @@ function show_current_preferences()
246392
247393 timestamp = Preferences. load_preference (LinearSolve, " autotune_timestamp" , " unknown" )
248394 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." )
249398end
0 commit comments