Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
8475266
Add autotune preference integration to default solver selection
ChrisRackauckas Aug 14, 2025
a28f52a
Optimize autotune preference integration with compile-time constants
ChrisRackauckas Aug 14, 2025
fea6b0c
Complete optimization with all requested improvements
ChrisRackauckas Aug 14, 2025
56a417d
Add algorithm availability checking and fallback system
ChrisRackauckas Aug 14, 2025
59ce71f
Add comprehensive tests for dual preference system integration in def…
ChrisRackauckas Aug 15, 2025
7f9bd67
Add explicit algorithm choice verification tests for dual preference …
ChrisRackauckas Aug 15, 2025
9484b72
Clean up algorithm choice tests and ensure proper preference reset
ChrisRackauckas Aug 15, 2025
5eda050
Add separate Preferences test group with FastLapack algorithm verific…
ChrisRackauckas Aug 15, 2025
5a3f480
Fix preference tests: only print on failure, correct extension-depend…
ChrisRackauckas Aug 15, 2025
913cded
Fix size category boundaries to match LinearSolveAutotune and add com…
ChrisRackauckas Aug 15, 2025
374aba5
Remove unnecessary success prints from FastLapack and RecursiveFactor…
ChrisRackauckas Aug 15, 2025
822ff6a
Add explicit algorithm choice verification for FastLapack and RFLU
ChrisRackauckas Aug 15, 2025
ee4f0b0
Add explicit algorithm choice tests: verify FastLU and RFLU selection…
ChrisRackauckas Aug 15, 2025
6af69d8
Apply suggestions from code review
ChrisRackauckas Aug 15, 2025
89bcb9e
Add comprehensive size category algorithm verification with different…
ChrisRackauckas Aug 15, 2025
6847dc5
Fix algorithm choice test to use AppleAccelerateLUFactorization from …
ChrisRackauckas Aug 15, 2025
19beb8d
Add comprehensive algorithm choice analysis function for testing and …
ChrisRackauckas Aug 15, 2025
a372fdb
Make preference tests strict: require exact algorithm match
ChrisRackauckas Aug 15, 2025
3240462
Remove boundary testing section as requested
ChrisRackauckas Aug 15, 2025
66faf95
Revert "Remove boundary testing section as requested"
ChrisRackauckas Aug 15, 2025
4958c38
Remove non-LU algorithms from _string_to_algorithm_choice
ChrisRackauckas Aug 15, 2025
beeec34
Move show_algorithm_choices to main package and simplify
ChrisRackauckas Aug 15, 2025
c55e420
Update documentation for dual preference system and show_algorithm_ch…
ChrisRackauckas Aug 15, 2025
3dc46f1
Update test/preferences.jl
ChrisRackauckas Aug 15, 2025
7ee156c
Fix FastLapack test to use GenericLUFactorization as always_loaded
ChrisRackauckas Aug 15, 2025
5161904
Add reset_defaults! function for testing preference system integration
ChrisRackauckas Aug 16, 2025
6150d55
Clean up preference system and enhance show_algorithm_choices display
ChrisRackauckas Aug 16, 2025
a86bd4c
Streamline preference tests with single reset_defaults! call
ChrisRackauckas Aug 16, 2025
a52b267
Move preference handling to dedicated src/preferences.jl file
ChrisRackauckas Aug 16, 2025
fbd7155
Fix preference tests: correct FastLU mapping and add preference isola…
ChrisRackauckas Aug 16, 2025
da8f72d
Replace algorithm test with robust RFLU vs GenericLU verification
ChrisRackauckas Aug 16, 2025
0e69356
Update test/preferences.jl
ChrisRackauckas Aug 16, 2025
9881aeb
Clean up preference system: remove analysis.jl, use eval-based testin…
ChrisRackauckas Aug 16, 2025
bb9c717
Rename reset_defaults! to make_preferences_dynamic!
ChrisRackauckas Aug 16, 2025
1be6f5b
Update documentation for final preference system implementation
ChrisRackauckas Aug 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/Tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
- "LinearSolvePardiso"
- "NoPre"
- "LinearSolveAutotune"
- "Preferences"
os:
- ubuntu-latest
- macos-latest
Expand Down
22 changes: 22 additions & 0 deletions docs/src/advanced/internal_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,30 @@ The automatic algorithm selection is one of LinearSolve.jl's key features:

```@docs
LinearSolve.defaultalg
LinearSolve.get_tuned_algorithm
LinearSolve.is_algorithm_available
LinearSolve.show_algorithm_choices
```

### Preference System Internals

The dual preference system provides intelligent algorithm selection with fallbacks:

- **`get_tuned_algorithm`**: Retrieves tuned algorithm preferences based on matrix size and element type
- **`is_algorithm_available`**: Checks if a specific algorithm is currently available (extensions loaded)
- **`show_algorithm_choices`**: Analysis function to display current algorithm choices and preferences

The system categorizes matrix sizes as:
- **tiny**: ≀20 elements
- **small**: 21-100 elements
- **medium**: 101-300 elements
- **large**: 301-1000 elements
- **big**: >1000 elements

For each category and element type, preferences store both:
- `best_algorithm_{type}_{size}`: Overall fastest algorithm
- `best_always_loaded_{type}_{size}`: Fastest always-available algorithm (fallback)

## Trait Functions

These trait functions help determine algorithm capabilities and requirements:
Expand Down
44 changes: 43 additions & 1 deletion docs/src/basics/algorithm_selection.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,46 @@ end
sol = solve(prob, LinearSolveFunction(my_custom_solver))
```

See the [Custom Linear Solvers](@ref custom) section for more details.
See the [Custom Linear Solvers](@ref custom) section for more details.

## Tuned Algorithm Selection

LinearSolve.jl includes a sophisticated preference system that can be tuned using LinearSolveAutotune for optimal performance on your specific hardware:

```julia
using LinearSolve
using LinearSolveAutotune

# Run autotune to benchmark algorithms and set preferences
results = autotune_setup(set_preferences = true)

# View what algorithms are now being chosen
show_algorithm_choices()
```

The system automatically sets preferences for:
- **Different matrix sizes**: tiny (≀20), small (21-100), medium (101-300), large (301-1000), big (>1000)
- **Different element types**: Float32, Float64, ComplexF32, ComplexF64
- **Dual preferences**: Best overall algorithm + best always-available fallback

### Viewing Algorithm Choices

Use `show_algorithm_choices()` to see what algorithms are currently being selected:

```julia
using LinearSolve
show_algorithm_choices()
```

This shows:
- Current autotune preferences (if set)
- Algorithm choices for each size category
- System information (available extensions)
- Element type behavior

### Preference System Benefits

- **Automatic optimization**: Uses the fastest algorithms found by benchmarking
- **Intelligent fallbacks**: Falls back to always-available algorithms when extensions aren't loaded
- **Size-specific tuning**: Different algorithms optimized for different matrix sizes
- **Type-specific tuning**: Optimized algorithm selection for different numeric types
46 changes: 29 additions & 17 deletions docs/src/tutorials/autotune.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

LinearSolve.jl includes an automatic tuning system that benchmarks all available linear algebra algorithms on your specific hardware and automatically selects optimal algorithms for different problem sizes and data types. This tutorial will show you how to use the `LinearSolveAutotune` sublibrary to optimize your linear solve performance.

!!! warn
The autotuning system is under active development. While benchmarking and result sharing are fully functional, automatic preference setting for algorithm selection is still being refined.
The autotuning system provides comprehensive benchmarking and automatic algorithm selection optimization for your specific hardware.

## Quick Start

Expand Down Expand Up @@ -418,33 +417,46 @@ for config in configs
end
```

## Preferences Integration
## Algorithm Selection Analysis

You can analyze what algorithms are currently being chosen for different matrix sizes:

```julia
using LinearSolve

!!! warn
Automatic preference setting is still under development and may not affect algorithm selection in the current version.
# Show current algorithm choices and preferences
show_algorithm_choices()
```

This displays:
- Current autotune preferences (if any are set)
- Algorithm choices for representative sizes in each category
- Element type behavior
- System information (MKL, Apple Accelerate, RecursiveFactorization status)

## Preferences Integration

The autotuner can set preferences that LinearSolve.jl will use for automatic algorithm selection:
The autotuner sets preferences that LinearSolve.jl uses for automatic algorithm selection:

```julia
using LinearSolveAutotune

# View current preferences (if any)
LinearSolveAutotune.show_current_preferences()

# Run autotune and set preferences
results = autotune_setup(set_preferences = true)

# Clear all autotune preferences
LinearSolveAutotune.clear_algorithm_preferences()
# View what algorithms are now being chosen
using LinearSolve
show_algorithm_choices()

# Manually set custom preferences
custom_categories = Dict(
"Float64_0-128" => "RFLUFactorization",
"Float64_128-256" => "LUFactorization"
)
LinearSolveAutotune.set_algorithm_preferences(custom_categories)
# View current preferences
LinearSolveAutotune.show_current_preferences()

# Clear all autotune preferences if needed
LinearSolveAutotune.clear_algorithm_preferences()
```

After running autotune with `set_preferences = true`, LinearSolve.jl will automatically use the fastest algorithms found for each matrix size and element type, with intelligent fallbacks when extensions are not available.

## Troubleshooting

### Common Issues
Expand Down
173 changes: 172 additions & 1 deletion src/LinearSolve.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ else
const usemkl = false
end


@reexport using SciMLBase

"""
Expand Down Expand Up @@ -276,6 +277,175 @@ EnumX.@enumx DefaultAlgorithmChoice begin
KrylovJL_LSMR
end

# Autotune preference constants - loaded once at package import time
# Helper function to convert algorithm name string to DefaultAlgorithmChoice enum
function _string_to_algorithm_choice(algorithm_name::Union{String, Nothing})
algorithm_name === nothing && return nothing

# Core LU algorithms from LinearSolveAutotune
if algorithm_name == "LUFactorization"
return DefaultAlgorithmChoice.LUFactorization
elseif algorithm_name == "GenericLUFactorization"
return DefaultAlgorithmChoice.GenericLUFactorization
elseif algorithm_name == "RFLUFactorization" || algorithm_name == "RecursiveFactorization"
return DefaultAlgorithmChoice.RFLUFactorization
elseif algorithm_name == "MKLLUFactorization"
return DefaultAlgorithmChoice.MKLLUFactorization
elseif algorithm_name == "AppleAccelerateLUFactorization"
return DefaultAlgorithmChoice.AppleAccelerateLUFactorization
elseif algorithm_name == "SimpleLUFactorization"
return DefaultAlgorithmChoice.LUFactorization # Map to standard LU
elseif algorithm_name == "FastLUFactorization"
return DefaultAlgorithmChoice.LUFactorization # Map to standard LU (FastLapack extension)
elseif algorithm_name == "BLISLUFactorization"
return DefaultAlgorithmChoice.LUFactorization # Map to standard LU (BLIS extension)
elseif algorithm_name == "CudaOffloadLUFactorization"
return DefaultAlgorithmChoice.LUFactorization # Map to standard LU (CUDA extension)
elseif algorithm_name == "MetalLUFactorization"
return DefaultAlgorithmChoice.LUFactorization # Map to standard LU (Metal extension)
elseif algorithm_name == "AMDGPUOffloadLUFactorization"
return DefaultAlgorithmChoice.LUFactorization # Map to standard LU (AMDGPU extension)
else
@warn "Unknown algorithm preference: $algorithm_name, falling back to heuristics"
return nothing
end
end

# Load autotune preferences as constants for each element type and size category
# Support both best overall algorithm and best always-loaded algorithm as fallback
const AUTOTUNE_PREFS = (
Float32 = (
tiny = (
best = _string_to_algorithm_choice(Preferences.@load_preference("best_algorithm_Float32_tiny", nothing)),
fallback = _string_to_algorithm_choice(Preferences.@load_preference("best_always_loaded_Float32_tiny", nothing))
),
small = (
best = _string_to_algorithm_choice(Preferences.@load_preference("best_algorithm_Float32_small", nothing)),
fallback = _string_to_algorithm_choice(Preferences.@load_preference("best_always_loaded_Float32_small", nothing))
),
medium = (
best = _string_to_algorithm_choice(Preferences.@load_preference("best_algorithm_Float32_medium", nothing)),
fallback = _string_to_algorithm_choice(Preferences.@load_preference("best_always_loaded_Float32_medium", nothing))
),
large = (
best = _string_to_algorithm_choice(Preferences.@load_preference("best_algorithm_Float32_large", nothing)),
fallback = _string_to_algorithm_choice(Preferences.@load_preference("best_always_loaded_Float32_large", nothing))
),
big = (
best = _string_to_algorithm_choice(Preferences.@load_preference("best_algorithm_Float32_big", nothing)),
fallback = _string_to_algorithm_choice(Preferences.@load_preference("best_always_loaded_Float32_big", nothing))
)
),
Float64 = (
tiny = (
best = _string_to_algorithm_choice(Preferences.@load_preference("best_algorithm_Float64_tiny", nothing)),
fallback = _string_to_algorithm_choice(Preferences.@load_preference("best_always_loaded_Float64_tiny", nothing))
),
small = (
best = _string_to_algorithm_choice(Preferences.@load_preference("best_algorithm_Float64_small", nothing)),
fallback = _string_to_algorithm_choice(Preferences.@load_preference("best_always_loaded_Float64_small", nothing))
),
medium = (
best = _string_to_algorithm_choice(Preferences.@load_preference("best_algorithm_Float64_medium", nothing)),
fallback = _string_to_algorithm_choice(Preferences.@load_preference("best_always_loaded_Float64_medium", nothing))
),
large = (
best = _string_to_algorithm_choice(Preferences.@load_preference("best_algorithm_Float64_large", nothing)),
fallback = _string_to_algorithm_choice(Preferences.@load_preference("best_always_loaded_Float64_large", nothing))
),
big = (
best = _string_to_algorithm_choice(Preferences.@load_preference("best_algorithm_Float64_big", nothing)),
fallback = _string_to_algorithm_choice(Preferences.@load_preference("best_always_loaded_Float64_big", nothing))
)
),
ComplexF32 = (
tiny = (
best = _string_to_algorithm_choice(Preferences.@load_preference("best_algorithm_ComplexF32_tiny", nothing)),
fallback = _string_to_algorithm_choice(Preferences.@load_preference("best_always_loaded_ComplexF32_tiny", nothing))
),
small = (
best = _string_to_algorithm_choice(Preferences.@load_preference("best_algorithm_ComplexF32_small", nothing)),
fallback = _string_to_algorithm_choice(Preferences.@load_preference("best_always_loaded_ComplexF32_small", nothing))
),
medium = (
best = _string_to_algorithm_choice(Preferences.@load_preference("best_algorithm_ComplexF32_medium", nothing)),
fallback = _string_to_algorithm_choice(Preferences.@load_preference("best_always_loaded_ComplexF32_medium", nothing))
),
large = (
best = _string_to_algorithm_choice(Preferences.@load_preference("best_algorithm_ComplexF32_large", nothing)),
fallback = _string_to_algorithm_choice(Preferences.@load_preference("best_always_loaded_ComplexF32_large", nothing))
),
big = (
best = _string_to_algorithm_choice(Preferences.@load_preference("best_algorithm_ComplexF32_big", nothing)),
fallback = _string_to_algorithm_choice(Preferences.@load_preference("best_always_loaded_ComplexF32_big", nothing))
)
),
ComplexF64 = (
tiny = (
best = _string_to_algorithm_choice(Preferences.@load_preference("best_algorithm_ComplexF64_tiny", nothing)),
fallback = _string_to_algorithm_choice(Preferences.@load_preference("best_always_loaded_ComplexF64_tiny", nothing))
),
small = (
best = _string_to_algorithm_choice(Preferences.@load_preference("best_algorithm_ComplexF64_small", nothing)),
fallback = _string_to_algorithm_choice(Preferences.@load_preference("best_always_loaded_ComplexF64_small", nothing))
),
medium = (
best = _string_to_algorithm_choice(Preferences.@load_preference("best_algorithm_ComplexF64_medium", nothing)),
fallback = _string_to_algorithm_choice(Preferences.@load_preference("best_always_loaded_ComplexF64_medium", nothing))
),
large = (
best = _string_to_algorithm_choice(Preferences.@load_preference("best_algorithm_ComplexF64_large", nothing)),
fallback = _string_to_algorithm_choice(Preferences.@load_preference("best_always_loaded_ComplexF64_large", nothing))
),
big = (
best = _string_to_algorithm_choice(Preferences.@load_preference("best_algorithm_ComplexF64_big", nothing)),
fallback = _string_to_algorithm_choice(Preferences.@load_preference("best_always_loaded_ComplexF64_big", nothing))
)
)
)

# Fast path: check if any autotune preferences are actually set
const AUTOTUNE_PREFS_SET = let
any_set = false
for type_prefs in (AUTOTUNE_PREFS.Float32, AUTOTUNE_PREFS.Float64, AUTOTUNE_PREFS.ComplexF32, AUTOTUNE_PREFS.ComplexF64)
for size_pref in (type_prefs.tiny, type_prefs.small, type_prefs.medium, type_prefs.large, type_prefs.big)
if size_pref.best !== nothing || size_pref.fallback !== nothing
any_set = true
break
end
end
any_set && break
end
any_set
end

# Algorithm availability checking functions
"""
is_algorithm_available(alg::DefaultAlgorithmChoice.T)

Check if the given algorithm is currently available (extensions loaded, etc.).
"""
function is_algorithm_available(alg::DefaultAlgorithmChoice.T)
if alg === DefaultAlgorithmChoice.LUFactorization
return true # Always available
elseif alg === DefaultAlgorithmChoice.GenericLUFactorization
return true # Always available
elseif alg === DefaultAlgorithmChoice.MKLLUFactorization
return usemkl # Available if MKL is loaded
elseif alg === DefaultAlgorithmChoice.AppleAccelerateLUFactorization
return appleaccelerate_isavailable() # Available on macOS with Accelerate
elseif alg === DefaultAlgorithmChoice.RFLUFactorization
return userecursivefactorization(nothing) # Requires RecursiveFactorization extension
else
# For extension-dependent algorithms not explicitly handled above,
# we cannot easily check availability without trying to use them.
# For now, assume they're not available in the default selection.
# This includes FastLU, BLIS, CUDA, Metal, etc. which would require
# specific extension checks.
return false
end
end

"""
DefaultLinearSolver(;safetyfallback=true)

Expand Down Expand Up @@ -309,6 +479,7 @@ include("simplelu.jl")
include("simplegmres.jl")
include("iterative_wrappers.jl")
include("preconditioners.jl")
include("analysis.jl")
include("solve_function.jl")
include("default.jl")
include("init.jl")
Expand Down Expand Up @@ -390,7 +561,7 @@ export LUFactorization, SVDFactorization, QRFactorization, GenericFactorization,
BunchKaufmanFactorization, CHOLMODFactorization, LDLtFactorization,
CUSOLVERRFFactorization, CliqueTreesFactorization

export LinearSolveFunction, DirectLdiv!
export LinearSolveFunction, DirectLdiv!, show_algorithm_choices

export KrylovJL, KrylovJL_CG, KrylovJL_MINRES, KrylovJL_GMRES,
KrylovJL_BICGSTAB, KrylovJL_LSMR, KrylovJL_CRAIGMR,
Expand Down
Loading
Loading