diff --git a/docs/src/api/array.md b/docs/src/api/array.md index 7d238d677..b6eb8848e 100644 --- a/docs/src/api/array.md +++ b/docs/src/api/array.md @@ -15,7 +15,7 @@ MtlVecOrMat ## Storage modes -The Metal API has various storage modes that dictate how a resource can be accessed. `MtlArray`s are `Metal.PrivateStorage` by default, but they can also be `Metal.SharedStorage` or `Metal.ManagedStorage`. For more information on storage modes, see the official [Metal documentation](https://developer.apple.com/documentation/metal/resource_fundamentals/setting_resource_storage_modes). +The Metal API has various storage modes that dictate how a resource can be accessed. `MtlArray`s are `Metal.SharedStorage` by default — zero-copy on unified-memory (Apple Silicon) GPUs — but they can also be `Metal.PrivateStorage` or `Metal.ManagedStorage`. On a discrete GPU, set the `default_storage` preference to `"private"` for best performance. For more information on storage modes, see the official [Metal documentation](https://developer.apple.com/documentation/metal/resource_fundamentals/setting_resource_storage_modes). ```@docs Metal.PrivateStorage diff --git a/src/array.jl b/src/array.jl index da6aa8a3c..8ad610f82 100644 --- a/src/array.jl +++ b/src/array.jl @@ -39,7 +39,7 @@ end `N`-dimensional Metal array with storage mode `S` and elements of type `T`. -`S` can be `Metal.PrivateStorage` (default), `Metal.SharedStorage`. +`S` can be `Metal.SharedStorage` (default) or `Metal.PrivateStorage`. See the Array Programming section of the Metal.jl docs for more details. """ @@ -190,14 +190,16 @@ See also `VecOrMat`(@ref) for examples. """ const MtlVecOrMat{T,S} = Union{MtlVector{T,S},MtlMatrix{T,S}} -# default to private memory -const DefaultStorageMode = let str = @load_preference("default_storage", "private") - if str == "private" - PrivateStorage - elseif str == "shared" +# Default to shared storage: zero-copy on unified-memory (Apple Silicon) GPUs and +# valid on discrete GPUs too (`__init__` steers discrete-GPU users to `private`). +# Override with the `default_storage` preference. +const DefaultStorageMode = let str = @load_preference("default_storage", "shared") + if str == "shared" SharedStorage + elseif str == "private" + PrivateStorage else - error("unknown default storage mode: $default_storage") + error("unknown default storage mode: $str (expected \"shared\" or \"private\")") end end @@ -475,9 +477,9 @@ function Adapt.adapt_storage(to::MtlArrayAdaptor{S}, xs::AbstractArray{T,N}) whe end """ - mtl(A; storage=Metal.PrivateStorage) + mtl(A; storage=Metal.SharedStorage) -`storage` can be `Metal.PrivateStorage` (default) or `Metal.SharedStorage`. +`storage` can be `Metal.SharedStorage` (default) or `Metal.PrivateStorage`. Opinionated GPU array adaptor, which may alter the element type `T` of arrays: * For `T<:AbstractFloat`, it makes a `MtlArray{Float32}` for performance and compatibility diff --git a/src/initialization.jl b/src/initialization.jl index 9d6bb9680..28aa4b48e 100644 --- a/src/initialization.jl +++ b/src/initialization.jl @@ -79,6 +79,19 @@ function __init__() return end + # The default `SharedStorage` is optimal on unified-memory (Apple Silicon) GPUs + # and works everywhere, but on a discrete GPU `PrivateStorage` is usually faster. + # Steer those (rare) users to the preference, unless they already chose a default. + if DefaultStorageMode == SharedStorage && + load_preference(Metal, "default_storage", nothing) === nothing && + functional() && !device().hasUnifiedMemory + @warn """Metal.jl defaults to `SharedStorage`, which is optimal on unified-memory + (Apple Silicon) GPUs. This GPU does not have unified memory, where + `PrivateStorage` is usually faster. To switch the default, run + using Preferences; set_preferences!(Metal, "default_storage" => "private") + and restart Julia.""" maxlog = 1 + end + # ensure that operations executed by the REPL back-end finish before returning, # because displaying values happens on a different task if isdefined(Base, :active_repl_backend) && !isnothing(Base.active_repl_backend) diff --git a/test/array.jl b/test/array.jl index b473f0c40..08e72f0ff 100644 --- a/test/array.jl +++ b/test/array.jl @@ -72,6 +72,12 @@ end @test Adapt.adapt(MtlMatrix{ComplexF32, Metal.SharedStorage}, [1 2;3 4]) isa MtlArray{ComplexF32, 2, Metal.SharedStorage} @test Adapt.adapt(MtlArray{Float16}, Float64[1]) isa MtlArray{Float16} + # MtlArrays default to shared storage; explicit storage modes still resolve. + @test Metal.DefaultStorageMode === Metal.SharedStorage + @test Metal.is_shared(MtlArray{Int}(undef, 4)) + @test Metal.is_shared(mtl([1.0f0, 2.0f0, 3.0f0])) + @test Metal.is_private(MtlArray{Int, 1, Metal.PrivateStorage}(undef, 4)) + # Test a few explicitly unsupported types @test_throws "MtlArray only supports element types that are stored inline" MtlArray(BigInt[1]) @test_throws "Metal does not support Float64 values" MtlArray(Float64[1])