diff --git a/.gitattributes b/.gitattributes index 87c816cbf..dfe077042 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,2 @@ # Auto detect text files and perform LF normalization * text=auto -.githash merge=ours diff --git a/benchmark/run.jl b/benchmark/run.jl index 574bb4ea7..cb748ccde 100644 --- a/benchmark/run.jl +++ b/benchmark/run.jl @@ -34,7 +34,7 @@ function ensure_project_ready() Pkg.PackageSpec(; path=joinpath(@__DIR__, "..", "lib", "CNPreferences")), Pkg.PackageSpec(; path=joinpath(@__DIR__, "..")), ]) - Pkg.instantiate() + return Pkg.instantiate() end # default env name mirrors install_cupynumeric.sh: cupynumeric-bench-. @@ -46,7 +46,7 @@ function cupynumeric_env_name() info.version === nothing && continue return "cupynumeric-bench-$(info.version.major).$(info.version.minor)" end - error("could not resolve cupynumeric_jll version; set CUPYNUMERIC_ENV explicitly") + return error("could not resolve cupynumeric_jll version; set CUPYNUMERIC_ENV explicitly") end function dispatch(; gpus, cpus, name, T, N, M, n_iter, n_warmup, n_trial, diff --git a/benchmark/src/benchmarks/gemm.jl b/benchmark/src/benchmarks/gemm.jl index a43567926..4f85d3e24 100644 --- a/benchmark/src/benchmarks/gemm.jl +++ b/benchmark/src/benchmarks/gemm.jl @@ -8,7 +8,7 @@ dims(g::GEMM) = (g.N, g.M) data(g::GEMM{T}) where {T} = "GEMM with T=$(T), N=$(g.N), M=$(g.M)" function allowed_types(::Type{GEMM}) - Union{cuNumeric.SUPPORTED_FLOAT_TYPES,cuNumeric.SUPPORTED_INT_TYPES} + return Union{cuNumeric.SUPPORTED_FLOAT_TYPES,cuNumeric.SUPPORTED_INT_TYPES} end total_flops(s::GEMM) = s.N * s.N * ((2*s.M) - 1) diff --git a/benchmark/src/benchmarks/grayscott.jl b/benchmark/src/benchmarks/grayscott.jl index 29f4151c6..3ba6e6398 100644 --- a/benchmark/src/benchmarks/grayscott.jl +++ b/benchmark/src/benchmarks/grayscott.jl @@ -150,7 +150,8 @@ let body = quote v_new[end, :] = v[2, :] end @eval _gs_step!(b::GrayScottBaseline, u, v, u_new, v_new, args::GSParams) = $body - @eval _gs_step!(b::GrayScottLifetimes, u, v, u_new, v_new, args::GSParams) = @analyze_lifetimes $body + @eval _gs_step!(b::GrayScottLifetimes, u, v, u_new, v_new, args::GSParams) = + @analyze_lifetimes $body end function run!(b::AbstractGrayScott, st::GrayScottState) diff --git a/benchmark/src/benchmarks/montecarlo.jl b/benchmark/src/benchmarks/montecarlo.jl index 1df91c97f..978e5906f 100644 --- a/benchmark/src/benchmarks/montecarlo.jl +++ b/benchmark/src/benchmarks/montecarlo.jl @@ -5,7 +5,7 @@ end name(::MonteCarloIntegration) = "montecarlo" dims(mci::MonteCarloIntegration) = (mci.n_samples, 1) function data(mci::MonteCarloIntegration{T}) where {T} - "Monte Carlo Integration with T=$(T), n_samples=$(mci.n_samples)" + return "Monte Carlo Integration with T=$(T), n_samples=$(mci.n_samples)" end allowed_types(::Type{MonteCarloIntegration}) = cuNumeric.SUPPORTED_FLOAT_TYPES @@ -25,7 +25,7 @@ run!(mci::MonteCarloIntegration, x) = _domain_volume(mci) * sum(exp.(-x .^ 2)) # n_samples comes in as N; M is unused. function build_benchmark(::Type{MonteCarloIntegration}, ::Type{T}, N, M) where {T} - MonteCarloIntegration{T}(; n_samples=N) + return MonteCarloIntegration{T}(; n_samples=N) end register_benchmark("montecarlo", MonteCarloIntegration) diff --git a/benchmark/src/core.jl b/benchmark/src/core.jl index 5d9b3abbb..526ce4eb1 100644 --- a/benchmark/src/core.jl +++ b/benchmark/src/core.jl @@ -43,11 +43,11 @@ function run! end # registers itself via `register_benchmark`. const BENCHMARKS = Dict{String,Type}() function register_benchmark(key::AbstractString, ::Type{B}) where {B<:AbstractBenchmark} - BENCHMARKS[key] = B + return BENCHMARKS[key] = B end function build_benchmark(::Type{B}, ::Type{T}, N, M) where {B<:AbstractBenchmark,T} - B{T}(; N=N, M=M) + return B{T}(; N=N, M=M) end ######################################### diff --git a/benchmark/src/parse_benchmarks.jl b/benchmark/src/parse_benchmarks.jl index cff2eba45..28cad96d4 100644 --- a/benchmark/src/parse_benchmarks.jl +++ b/benchmark/src/parse_benchmarks.jl @@ -23,7 +23,7 @@ function parse_fusion(x) s = lowercase(string(x)) s in ("on", "true") && return true s in ("off", "false") && return false - error("fusion must be on/off (or true/false); got $(repr(x))") + return error("fusion must be on/off (or true/false); got $(repr(x))") end # Value of a zipped field for sweep position `i`. length==1 field broadcasts. diff --git a/benchmark/src/single.jl b/benchmark/src/single.jl index e37f1d399..56ecd171b 100644 --- a/benchmark/src/single.jl +++ b/benchmark/src/single.jl @@ -57,7 +57,7 @@ function run_single( @printf("[%s] Mean Run Time: %.5f ± %.5f ms\n", label, mean(br.times_ms), _std(br.times_ms)) @printf("[%s] FLOPS: %.5f ± %.5f GFLOPS\n", label, mean(br.gflops), _std(br.gflops)) println("[$(label)] Correctness: $(br.correctness)") - save_result(br, gpus; mod=save_as) + return save_result(br, gpus; mod=save_as) end gpus = parse(Int, ARGS[1]) diff --git a/deps/build.jl b/deps/build.jl index 9bcdd2ad0..0e0553adb 100644 --- a/deps/build.jl +++ b/deps/build.jl @@ -38,7 +38,7 @@ function build_cpp_wrapper( @info "libcunumeric_jl_wrapper: Building C++ Wrapper Library" isdir(install_root) && (rm(install_root; recursive=true); mkdir(install_root)) bld_command = `$(joinpath(repo_root, "scripts/build_cpp_wrapper.sh")) $repo_root $cupynumeric_loc $legate_loc $blas_loc $install_root $(Threads.nthreads())` - BuildTools.run_build_wrapper_script( + return BuildTools.run_build_wrapper_script( repo_root, bld_command; cuda_root, cuda_enabled, log_dir=@__DIR__ ) end @@ -64,7 +64,7 @@ function build_deps(pkg_root, cupynumeric_root, blas_root; cuda_root=nothing, cu install_lib; cuda_root, cuda_enabled, ) - BuildTools.set_jll_artifact_override(:cunumeric_jl_wrapper_jll, install_lib) + return BuildTools.set_jll_artifact_override(:cunumeric_jl_wrapper_jll, install_lib) end function build(::CNPreferences.JLL) @@ -90,7 +90,7 @@ function build(::CNPreferences.Conda) #!TODO SET LocalPreferences.toml to use local CUDA libraries is_cupynumeric_installed(cupynumeric_root; throw_errors=true) - build_deps(pkg_root, cupynumeric_root, cupynumeric_root) + return build_deps(pkg_root, cupynumeric_root, cupynumeric_root) end function build(::CNPreferences.Developer) @@ -110,7 +110,7 @@ function build(::CNPreferences.Developer) end blas_lib = something(blas_lib, BuildTools.find_jll_artifact_dir(:OpenBLAS32_jll)) - build_deps(pkg_root, cupynumeric_root, up_dir(blas_lib); cuda_root, cuda_enabled) + return build_deps(pkg_root, cupynumeric_root, up_dir(blas_lib); cuda_root, cuda_enabled) end const mode_str = load_preference(CNPreferences, "cunumeric_mode", CNPreferences.MODE_JLL) diff --git a/docs/make.jl b/docs/make.jl index bb6f78260..027b38f04 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -38,7 +38,6 @@ makedocs(; "Initialization" => "examples/initialization.md", "Monte-Carlo" => "examples/montecarlo.md", "Gray-Scott" => "examples/grayscott.md", - "HDF5 I/O" => "examples/hdf5.md", ], "Performance Tips" => [ "Kernel Fusion" => "perf/kernel_fusion.md", diff --git a/docs/src/api_hdf5.md b/docs/src/api_hdf5.md index 27b97f3f1..c60787935 100644 --- a/docs/src/api_hdf5.md +++ b/docs/src/api_hdf5.md @@ -1,29 +1,51 @@ # HDF5 -> [!NOTE] -> HDF5 support is planned. Signatures below are placeholders and will be replaced with `@docs` blocks when the API is implemented. +`h5read` and `h5write` transfer datasets between HDF5 files and runtime-managed +`NDArray`s without gathering them into Julia `Array`s. -I/O helpers for reading and writing `NDArray`s via HDF5. Prefer these over host-side gather + HDF5.jl when arrays are large or distributed. - -## h5read +## Example ```julia -# Planned: -# cuNumeric.h5read(path, dataset) -> NDArray +using cuNumeric + +field = cuNumeric.fill(3.5f0, 128, 64) +cuNumeric.h5write("checkpoint.h5", "field", field) +cuNumeric.Legate.runtime_sync() + +restored = cuNumeric.h5read("checkpoint.h5", "field"; layout=:row) +@assert size(restored) == (128, 64) +@assert eltype(restored) == Float32 +@assert cuNumeric.compare(fill(3.5f0, 128, 64), restored, 0, 0) ``` -Load a dataset from an HDF5 file into an `NDArray`. +`h5write` submits work to Legate and can return before the file write has completed. +Synchronize before accessing the file outside the runtime, moving or deleting it, or +exiting immediately after the write. -## h5write +## Dataset layout ```julia -# Planned: -# cuNumeric.h5write(path, dataset, arr::NDArray) +row_major = cuNumeric.h5read("python.h5", "field") +column_major = cuNumeric.h5read("julia.h5", "field"; layout=:col) ``` -Write an `NDArray` to an HDF5 dataset. +`layout=:row` is the default for NumPy/h5py, cuPyNumeric, and `cuNumeric.h5write`. +Use `layout=:col` for multidimensional datasets written by HDF5.jl. One-dimensional +datasets are unaffected. Other keywords are forwarded to `Legate.h5read`. + +Tests cover `Float32`, `Float64`, `Int32`, and `Int64` arrays with one to three +dimensions. Other types depend on the Legate HDF5 backend. + +## API reference -## Related +### h5read -- Example sketch: [HDF5 I/O](./examples/hdf5.md) -- Host conversion when you must leave the runtime: `Array(arr)` (see [NDArray Reference](./api.md)) +```@docs +cuNumeric.h5read +``` + +### h5write + +```@docs +cuNumeric.h5write +``` diff --git a/docs/src/examples/hdf5.md b/docs/src/examples/hdf5.md deleted file mode 100644 index 7c46104b2..000000000 --- a/docs/src/examples/hdf5.md +++ /dev/null @@ -1,22 +0,0 @@ -# HDF5 I/O - -> [!NOTE] -> HDF5 support is planned. This page is a placeholder for an end-to-end example once the API lands. - -Reading and writing `NDArray`s through HDF5 will let you checkpoint distributed arrays and exchange data with NumPy / cuPyNumeric workflows without gathering everything to the host first. - -## Planned sketch - -```julia -using cuNumeric - -# Write (API names TBD) -# cuNumeric.h5write("checkpoint.h5", "fields/u", u) - -# Read into an NDArray (API names TBD) -# u = cuNumeric.h5read("checkpoint.h5", "fields/u") -``` - -When available, prefer the cuNumeric HDF5 entry points over collecting to a Julia `Array` and using HDF5.jl alone, so large arrays can stay partitioned across devices. - -See [HDF5](../api_hdf5.md) in the Public API for the (forthcoming) function reference. diff --git a/src/cuda/cuda_util.jl b/src/cuda/cuda_util.jl index 68b141c77..fc2d5ea03 100644 --- a/src/cuda/cuda_util.jl +++ b/src/cuda/cuda_util.jl @@ -14,7 +14,7 @@ end # Dense @cuda_task / RunPTXTask — MUST match CUDA.jl CuDeviceArray layout. # Other memory types: https://github.com/JuliaGPU/CUDA.jl/blob/345c1600ebd561135148bb04ee2657f521a40e25/CUDACore/src/device/pointer.jl#L7 function ndarray_cuda_type(::Type{<:NDArray{T,N}}) where {T,N} - CUDACore.CuDeviceArray{T,N,CUDACore.AS.Global} + return CUDACore.CuDeviceArray{T,N,CUDACore.AS.Global} end function ndarray_cuda_type(::Type{T}) where {T} diff --git a/src/cuda/strided_device_array.jl b/src/cuda/strided_device_array.jl index 58efd836e..576281b7a 100644 --- a/src/cuda/strided_device_array.jl +++ b/src/cuda/strided_device_array.jl @@ -36,12 +36,12 @@ Base.length(a::CuStridedDeviceArray) = a.len Base.IndexStyle(::Type{<:CuStridedDeviceArray}) = IndexLinear() function Base.pointer(a::CuStridedDeviceArray{T,<:Any,A}) where {T,A} - Base.unsafe_convert(CUDACore.LLVMPtr{T,A}, a) + return Base.unsafe_convert(CUDACore.LLVMPtr{T,A}, a) end function Base.unsafe_convert( ::Type{CUDACore.LLVMPtr{T,A}}, a::CuStridedDeviceArray{T,<:Any,A} ) where {T,A} - a.ptr + return a.ptr end # 0-based element offset from a 1-based linear index in Julia column-major order @@ -90,9 +90,10 @@ CUDACore.@device_function @inline function _strided_arrayset( return A end -Base.@propagate_inbounds Base.getindex(A::CuStridedDeviceArray{T}, i::Integer) where {T} = _strided_arrayref( - A, i -) +Base.@propagate_inbounds Base.getindex(A::CuStridedDeviceArray{T}, i::Integer) where {T} = + _strided_arrayref( + A, i + ) Base.@propagate_inbounds function Base.setindex!( A::CuStridedDeviceArray{T}, x, i::Integer ) where {T} diff --git a/src/memory.jl b/src/memory.jl index 4a3442940..f1130230d 100644 --- a/src/memory.jl +++ b/src/memory.jl @@ -38,7 +38,7 @@ function init_gc!() total_device_bytes[] = query_total_device_memory() total_host_bytes[] = query_total_host_memory() # @info "[cuNumeric GC] $(total_device_bytes[]) framebuffer available" - AUTO_GC_ENABLE[] = true + return AUTO_GC_ENABLE[] = true end @doc""" diff --git a/src/ndarray/detail/ndarray.jl b/src/ndarray/detail/ndarray.jl index dfae001fc..6db3e7917 100644 --- a/src/ndarray/detail/ndarray.jl +++ b/src/ndarray/detail/ndarray.jl @@ -419,8 +419,7 @@ end # return underlying logical store to the NDArray obj function get_store(arr::NDArray) cxx_ptr = CxxWrap.CxxPtr{CN_NDArray}(arr.ptr) - store = _get_store(cxx_ptr) - return store + return _get_store(cxx_ptr) end function get_ptr(arr::NDArray{T,N}) where {T,N} diff --git a/src/ndarray/ndarray.jl b/src/ndarray/ndarray.jl index d06a65bef..09265c216 100644 --- a/src/ndarray/ndarray.jl +++ b/src/ndarray/ndarray.jl @@ -146,7 +146,7 @@ end function (::Type{<:Array{A}})(arr::NDArray{B,0}) where {A,B} out = Array{A}(undef) allowscalar() do - out[] = convert(A, arr[]) + return out[] = convert(A, arr[]) end return out end @@ -279,33 +279,33 @@ Base.IndexStyle(::NDArray) = IndexCartesian() function Base.show(io::IO, arr::NDArray{T,0}) where {T} allowscalar() do - print(io, "NDArray{$(T),0}(", repr(arr[]), ")") + return print(io, "NDArray{$(T),0}(", repr(arr[]), ")") end end function Base.show(io::IO, ::MIME"text/plain", arr::NDArray{T,0}) where {T} println(io, "0-dimensional NDArray{$(T),0}") allowscalar() do - print(io, arr[]) + return print(io, arr[]) end end function Base.show(io::IO, arr::NDArray{T,N}) where {T,N} - print(io, "NDArray{$(T),$(N)} with size ", size(arr)) + return print(io, "NDArray{$(T),$(N)} with size ", size(arr)) end function Base.show(io::IO, ::MIME"text/plain", arr::NDArray{T,N}) where {T,N} println(io, "NDArray{$(T),$(N)} with size ", size(arr)) - Base.print_array(io, Array(arr)) + return Base.print_array(io, Array(arr)) end function Base.print(arr::NDArray{T}) where {T} - Base.show(stdout, arr) + return Base.show(stdout, arr) end function Base.println(arr::NDArray{T}) where {T} Base.show(stdout, arr) - print("\n") + return print("\n") end #### ARRAY INDEXING AND SLICES #### @@ -370,39 +370,39 @@ end #! TODO SUPPORT CONVERSION OF VALUES function Base.setindex!(arr::NDArray{T,N}, value::T, idxs::Vararg{Int,N}) where {T,N} assertscalar("setindex!") - _setindex!(Val{N}(), arr, value, idxs...) + return _setindex!(Val{N}(), arr, value, idxs...) end function Base.setindex!(arr::NDArray{Complex{T},N}, value::T, idxs::Vararg{Int,N}) where {T,N} assertscalar("setindex!") - _setindex!(Val{N}(), arr, Complex{T}(value), idxs...) + return _setindex!(Val{N}(), arr, Complex{T}(value), idxs...) end function Base.setindex!(arr::NDArray{T,N}, value, idxs::Vararg{Int,N}) where {T,N} assertscalar("setindex!") - _setindex!(Val{N}(), arr, convert(T, value), idxs...) + return _setindex!(Val{N}(), arr, convert(T, value), idxs...) end function _setindex!(::Val{0}, arr::NDArray{T,0}, value::T) where {T<:SUPPORTED_NUMERIC_TYPES} acc = NDArrayAccessor{T,1}() - write(acc, arr.ptr, StdVector(UInt64[0]), value) + return write(acc, arr.ptr, StdVector(UInt64[0]), value) end function _setindex!(::Val{0}, arr::NDArray{Bool,0}, value::Bool) acc = NDArrayAccessor{CxxWrap.CxxBool,1}() - write(acc, arr.ptr, StdVector(UInt64[0]), value) + return write(acc, arr.ptr, StdVector(UInt64[0]), value) end function _setindex!( ::Val{N}, arr::NDArray{T,N}, value::T, idxs::Vararg{Int,N} ) where {T<:SUPPORTED_NUMERIC_TYPES,N} acc = NDArrayAccessor{T,N}() - write(acc, arr.ptr, to_cpp_index(idxs), value) + return write(acc, arr.ptr, to_cpp_index(idxs), value) end function _setindex!(::Val{N}, arr::NDArray{Bool,N}, value::Bool, idxs::Vararg{Int,N}) where {N} acc = NDArrayAccessor{CxxWrap.CxxBool,N}() - write(acc, arr.ptr, to_cpp_index(idxs), value) + return write(acc, arr.ptr, to_cpp_index(idxs), value) end #### START OF SLICING #### @@ -417,35 +417,35 @@ function _setindex_slice!(lhs::NDArray, rhs::NDArray, slices) end function Base.setindex!(lhs::NDArray, rhs::NDArray, i::Colon, j::Int64) - _setindex_slice!(lhs, rhs, slice_array((0, Base.size(lhs, 1)), (j-1, j))) + return _setindex_slice!(lhs, rhs, slice_array((0, Base.size(lhs, 1)), (j-1, j))) end function Base.setindex!(lhs::NDArray, rhs::NDArray, i::Int64, j::Colon) - _setindex_slice!(lhs, rhs, slice_array((i-1, i))) + return _setindex_slice!(lhs, rhs, slice_array((i-1, i))) end function Base.setindex!(lhs::NDArray, rhs::NDArray, i::UnitRange, j::Colon) - _setindex_slice!( + return _setindex_slice!( lhs, rhs, slice_array((first(i) - 1, last(i)), (0, Base.size(lhs, 2))) ) end function Base.setindex!(lhs::NDArray, rhs::NDArray, i::Colon, j::UnitRange) - _setindex_slice!( + return _setindex_slice!( lhs, rhs, slice_array((0, Base.size(lhs, 1)), (first(j) - 1, last(j))) ) end function Base.setindex!(lhs::NDArray, rhs::NDArray, i::UnitRange, j::Int64) - _setindex_slice!(lhs, rhs, slice_array((first(i) - 1, last(i)), (j-1, j))) + return _setindex_slice!(lhs, rhs, slice_array((first(i) - 1, last(i)), (j-1, j))) end function Base.setindex!(lhs::NDArray, rhs::NDArray, i::Int64, j::UnitRange) - _setindex_slice!(lhs, rhs, slice_array((i-1, i), (first(j) - 1, last(j)))) + return _setindex_slice!(lhs, rhs, slice_array((i-1, i), (first(j) - 1, last(j)))) end function Base.setindex!(lhs::NDArray, rhs::NDArray, i::UnitRange, j::UnitRange) - _setindex_slice!( + return _setindex_slice!( lhs, rhs, slice_array((first(i) - 1, last(i)), (first(j) - 1, last(j))) ) end @@ -492,19 +492,19 @@ end Base.getindex(arr::NDArray{T}, c::Vararg{Colon,N}) where {T,N} = Base.copy(arr) function Base.setindex!(arr::NDArray{T}, rhs::NDArray{T}, c::Vararg{Colon,N}) where {T,N} - Base.copyto!(arr, rhs) + return Base.copyto!(arr, rhs) end function Base.setindex!(arr::NDArray{T,2}, val::T, i::Colon, j::Int64) where {T} s = nda_get_slice(arr, slice_array((0, Base.size(arr, 1)), (j-1, j))) nda_fill_array(s, val) - destroy!(s) + return destroy!(s) end function Base.setindex!(arr::NDArray{T,2}, val::T, i::Int64, j::Colon) where {T} s = nda_get_slice(arr, slice_array((i-1, i))) nda_fill_array(s, val) - destroy!(s) + return destroy!(s) end Base.fill!(arr::NDArray{T}, val::T) where {T} = nda_fill_array(arr, val) @@ -653,7 +653,7 @@ Fill `arr` in-place with uniform random `Float64` values. """ Random.rand!(arr::NDArray{Float64}) = cuNumeric.nda_random(arr, 0) function Random.rand!(arr::NDArray{T}) where {T} - error("rand! only supports NDArray{Float64} for now. Cast with cuNumeric.as_type.") + return error("rand! only supports NDArray{Float64} for now. Cast with cuNumeric.as_type.") end # Backend only generates Float64. Same-type path needs no cast; other floats @@ -828,3 +828,42 @@ end function Base.isapprox(arr::NDArray{T}, arr2::NDArray{T}; atol=0, rtol=0) where {T} return compare(arr, arr2, atol, rtol) end + +""" + h5write(path::String, dataset::String, arr::NDArray) + +Write an `NDArray` directly to an HDF5 dataset without a host copy or dimension flip. + +# Arguments +- `path`: Path to the HDF5 file. +- `dataset`: Name of the dataset to write. +- `arr`: The array to write. +""" +function h5write(path::String, dataset::String, arr::NDArray{T,N}) where {T,N} + st_handle = get_store(arr) + # NDArrays are row-major, so this writes straight through (no dim flip, no warning). + la = Legate.LogicalArray{T,N}(st_handle, size(arr)) + return Legate.h5write(path, dataset, la) +end + +""" + h5read(path::String, dataset::String; layout::Symbol=:row) -> NDArray + +Read a dataset from an HDF5 file into an `NDArray`. + +# Arguments +- `path`: Path to the HDF5 file. +- `dataset`: Name of the dataset to read. + +# Keywords +- `layout`: On-disk memory order, either `:row` (default) or `:col`. +""" +function h5read(path::String, dataset::String; kwargs...) + la = Legate.h5read(path, dataset; kwargs...) + T = eltype(la) + N = Int(Legate.dim(la)) + st = Legate.data(la.handle) # call data on the raw impl + ptr = nda_store_to_ndarray(st) # pass directly + arr = NDArray(ptr, T, Val(N), nothing) + return la.order === :col && N > 1 ? transpose(arr) : arr +end diff --git a/src/ndarray/promotion.jl b/src/ndarray/promotion.jl index b9dcbac0e..b13437fbb 100644 --- a/src/ndarray/promotion.jl +++ b/src/ndarray/promotion.jl @@ -34,12 +34,12 @@ end function __checked_promote_op( f::typeof(Base.literal_pow), a::Type{Tuple{_,ARR_TYPE,Val{-1}}} ) where {_,ARR_TYPE} - __recip_type(ARR_TYPE) + return __recip_type(ARR_TYPE) end function __checked_promote_op( f::typeof(Base.literal_pow), a::Type{Tuple{_,ARR_TYPE,Val{2}}} ) where {_,ARR_TYPE} - ARR_TYPE + return ARR_TYPE end __checked_promote_op(::typeof(Base.inv), ::Type{Tuple{A}}) where {A} = __recip_type(A) @@ -74,7 +74,7 @@ end # Base.promote_op(^, Float32, Int64) == Float32 # Base.promote_op(^, Int32, Int64) == Int32 function __my_promote_type(::Type{typeof(^)}, ::Type{A}, ::Type{Val{V}}) where {A,V} - __checked_promote_op(Base.:(^), A, typeof(V)) + return __checked_promote_op(Base.:(^), A, typeof(V)) end #! Not exaclty Julia behavior, but it it makes life easier... diff --git a/src/util.jl b/src/util.jl index 5ca776267..17a39b7af 100644 --- a/src/util.jl +++ b/src/util.jl @@ -33,7 +33,7 @@ Insert a Legate execution fence. `block=true` waits until prior ops finish; issue_execution_fence(; block::Bool=false) = Legate.issue_execution_fence(block) function Experimental(setting::Bool) - task_local_storage(:Experimental, setting) + return task_local_storage(:Experimental, setting) end function assert_experimental() diff --git a/src/utilities/preference.jl b/src/utilities/preference.jl index 1b2852d2f..36868fc72 100644 --- a/src/utilities/preference.jl +++ b/src/utilities/preference.jl @@ -67,7 +67,7 @@ function find_paths( CNPreferences.to_mode(mode), cupynumeric_jll_module, cupynumeric_jll_wrapper_module ) set_preferences!(CNPreferences, "CUPYNUMERIC_LIBDIR" => libcupynumeric_path; force=true) - set_preferences!( + return set_preferences!( CNPreferences, "CUPYNUMERIC_WRAPPER_LIBDIR" => libcupynumeric_wrapper_path; force=true ) end diff --git a/src/utilities/version.jl b/src/utilities/version.jl index 5dabd527e..75d9cbca9 100644 --- a/src/utilities/version.jl +++ b/src/utilities/version.jl @@ -90,5 +90,5 @@ function versioninfo(io::IO=stdout) Legate: $(LegatePreferences.MODE) ─────────────────────────────────────────────── """ - println(io, str) + return println(io, str) end diff --git a/test/runtests.jl b/test/runtests.jl index 03caf4e68..7ec03491e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -59,6 +59,7 @@ include("tests/unary_tests.jl") include("tests/binary_tests.jl") include("tests/scoping.jl") include("tests/scoping-advanced.jl") +include("tests/hdf5.jl") include("tests/broadcast_fusion_tests.jl") @testset verbose = true "AXPY" begin @@ -463,6 +464,14 @@ end end end +@testset verbose = true "HDF5" begin + for T in (Float32, Float64, Int32, Int64) + @testset "$T $shape" for shape in ((7,), (3, 4), (2, 3, 4)) + test_hdf5_roundtrip(T, shape) + end + end +end + if run_gpu_tests @testset verbose = true "Broadcast Fusion" begin test_broadcast_fusion() diff --git a/test/tests/hdf5.jl b/test/tests/hdf5.jl new file mode 100644 index 000000000..159152b01 --- /dev/null +++ b/test/tests/hdf5.jl @@ -0,0 +1,48 @@ +#= Copyright 2026 Northwestern University, + * Carnegie Mellon University University + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author(s): David Krasowska + * Ethan Meitz + * Nader Rahal +=# + +function test_hdf5_roundtrip(::Type{T}, shape::Tuple) where {T} + expected = reshape(T.(1:prod(shape)), shape) + input = cuNumeric.zeros(T, shape...) + @allowscalar for index in CartesianIndices(shape) + input[Tuple(index)...] = expected[index] + end + + return mktempdir() do dir + path = joinpath(dir, "roundtrip.h5") + dataset = "values" + + cuNumeric.h5write(path, dataset, input) + cuNumeric.Legate.runtime_sync() + @test isfile(path) + + output = cuNumeric.h5read(path, dataset; layout=:row) + @test eltype(output) == T + @test size(output) == shape + @allowscalar @test cuNumeric.compare(expected, output, 0, 0) + + if length(shape) > 1 + col_output = cuNumeric.h5read(path, dataset; layout=:col) + col_expected = permutedims(expected, reverse(1:length(shape))) + @test size(col_output) == reverse(shape) + @allowscalar @test cuNumeric.compare(col_expected, col_output, 0, 0) + end + end +end diff --git a/test/tests/util.jl b/test/tests/util.jl index a477d0576..17b52e2e6 100644 --- a/test/tests/util.jl +++ b/test/tests/util.jl @@ -44,7 +44,7 @@ is_same(arr1::Array, arr2::NDArray) = @allowscalar (arr1 == arr2)[1] is_same(arr1::Array, arr2::Array) = (arr1 == arr2) function my_rand(::Type{F}, dims...; L=F(-1000), R=F(1000)) where {F<:AbstractFloat} - L .+ (R-L) .* rand(F, dims...) + return L .+ (R-L) .* rand(F, dims...) end function my_rand(::Type{I}, dims...; L=nothing, R=nothing) where {I<:Integer} L_default = I <: Unsigned ? 0 : max(-255, Int64(typemin(I))) @@ -55,7 +55,7 @@ function my_rand(::Type{I}, dims...; L=nothing, R=nothing) where {I<:Integer} return floor.(I, res) end function my_rand(::Type{CT}, dims...; L=T(-100), R=T(100)) where {T,CT<:Complex{T}} - Complex.(my_rand(T, dims...; L=L, R=R), my_rand(T, dims...; L=L, R=R)) + return Complex.(my_rand(T, dims...; L=L, R=R), my_rand(T, dims...; L=L, R=R)) end my_rand(::Type{Bool}, dims...) = rand(Bool, dims...)