Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions benchmark/run.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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-<major>.<minor>
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion benchmark/src/benchmarks/gemm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion benchmark/src/benchmarks/grayscott.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions benchmark/src/benchmarks/montecarlo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
4 changes: 2 additions & 2 deletions benchmark/src/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

#########################################
Expand Down
2 changes: 1 addition & 1 deletion benchmark/src/parse_benchmarks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion benchmark/src/single.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
8 changes: 4 additions & 4 deletions deps/build.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
64 changes: 39 additions & 25 deletions docs/src/linalg.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
# Linear Algebra

cuNumeric.jl supports a small set of linear algebra operations on `NDArray`. This page covers matrix multiply, batched solve, and related helpers. Related autodocs also appear under [NDArray Reference](./api.md).
cuNumeric.jl provides matrix multiplication, batched solves, SVD, QR, and related
helpers for `NDArray`.

`solve`, `svd`, and `qr` accept `Float32`, `Float64`, `ComplexF32`, and
`ComplexF64`. Integer and `Bool` inputs require `@allowpromotion` or
`allowpromotion` and produce `Float64` outputs.

## Matrix multiply

For two 2D arrays, `*` is matrix multiplication (GEMM), not elementwise multiply. Use `.*` when you want an elementwise product of matrices.
For two 2D arrays, `*` performs matrix multiplication; use `.*` for an
elementwise product.

```julia
using LinearAlgebra
Expand All @@ -25,43 +31,50 @@ Filter = t -> t isa Function && nameof(t) === :mul!

## Solve (batched)

`cuNumeric.solve(A, b)` solves linear systems. It is not Julia's `\`.

```@docs
cuNumeric.solve
```

Shapes follow the batched signature:

- `A` is `(..., m, m)` (last two dims square)
- `b` is `(..., m)` or `(..., m, n)`
- result is `(..., m)` or `(..., m, n)`

A 1D right-hand side is reshaped internally to a single column, then reshaped back.
`cuNumeric.solve(A, b)` solves linear systems and returns an array with the same
shape as `b`. `A` has shape `(..., m, m)` and `b` has shape `(..., m)` or
`(..., m, n)`.

```julia
using cuNumeric

# Single system: (m, m) and (m,)
A = cuNumeric.rand(Float32, 64, 64)
b = cuNumeric.rand(Float32, 64)
x = cuNumeric.solve(A, b)

# Several right-hand sides: (m, m) and (m, n)
B = cuNumeric.rand(Float32, 64, 4)
X = cuNumeric.solve(A, B)

# Batched systems: (batch, m, m) and (batch, m, n)
As = cuNumeric.rand(Float32, 8, 32, 32)
Bs = cuNumeric.rand(Float32, 8, 32, 2)
Xs = cuNumeric.solve(As, Bs)
```

Notes:
## Singular value decomposition

`cuNumeric.svd(A, full_matrices=true)` returns `(U, S, Vh)` for a 2D `m × n`
array. With `k = min(m, n)`, the output shapes are:

- Full: `U` is `m × m`, `S` has length `k`, and `Vh` is `n × n`.
- Thin: `U` is `m × k`, `S` has length `k`, and `Vh` is `k × n`.

```julia
A = cuNumeric.rand(Float32, 128, 64)
U, S, Vh = cuNumeric.svd(A, false)
```

`S` is real-valued for both real and complex inputs.

## QR decomposition

`cuNumeric.qr(A)` returns the economy-size factors `(Q, R)` for a 2D `m × n`
array. With `k = min(m, n)`, `Q` is `m × k` and `R` is `k × n`.

```julia
A = cuNumeric.rand(Float32, 128, 64)
Q, R = cuNumeric.qr(A)
```

- Accepted types: `Float32`, `Float64`, `ComplexF32`, `ComplexF64`. Integer or `Bool` inputs promote to `Float64` only when promotion is allowed (`@allowpromotion` / `allowpromotion`).
- The implementation always goes through a batched Legate `SOLVE` task, including the 2D case.
- Batch dimensions are supported in the API. Coverage for higher-rank batches in the test suite is still thin, so start with 2D and small batches when validating new code.
SVD and QR currently accept only 2D arrays; batched decompositions are not
supported.

## Helpers

Expand All @@ -74,4 +87,5 @@ These helpers live on `NDArray` and are also listed in the Public API:

## Not available yet

There is no public `svd`, `qr`, `cholesky`, `eig`, `lu`, matrix `inv`, or `ldiv!` in cuNumeric.jl yet. Elementwise `inv` / `^-1` exist as unary ops; those are not matrix inverse.
There is no public `cholesky`, `eig`, `lu`, matrix `inv`, or `ldiv!` yet.
Elementwise `inv` / `^-1` are unary operations, not matrix inverse.
6 changes: 6 additions & 0 deletions lib/cunumeric_jl_wrapper/src/types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,10 @@ void wrap_linalg_ops(jlcxx::Module& mod) {
legate::LocalTaskID{CuPyNumericOpCode::CUPYNUMERIC_SOLVE});
mod.set_const("MP_SOLVE",
legate::LocalTaskID{CuPyNumericOpCode::CUPYNUMERIC_MP_SOLVE});
mod.set_const("SVD", legate::LocalTaskID{CuPyNumericOpCode::CUPYNUMERIC_SVD});
mod.set_const("CQR", legate::LocalTaskID{CuPyNumericOpCode::CUPYNUMERIC_QR});
mod.set_const("SYEV",
legate::LocalTaskID{CuPyNumericOpCode::CUPYNUMERIC_SYEV});
mod.set_const("GEEV",
legate::LocalTaskID{CuPyNumericOpCode::CUPYNUMERIC_GEEV});
}
4 changes: 3 additions & 1 deletion src/cuNumeric.jl
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ const SUPPORTED_NUMERIC_TYPES = Union{

# solve has no integer backend kernel
const SUPPORTED_SOLVE_TYPES = Union{SUPPORTED_FLOAT_TYPES,SUPPORTED_COMPLEX_TYPES}

const SUPPORTED_SVD_TYPES = Union{SUPPORTED_FLOAT_TYPES,SUPPORTED_COMPLEX_TYPES}
const SUPPORTED_QR_TYPES = Union{SUPPORTED_FLOAT_TYPES,SUPPORTED_COMPLEX_TYPES}
const SUPPORTED_ARRAY_TYPES = Union{Bool,SUPPORTED_NUMERIC_TYPES}
const SUPPORTED_TYPES = Union{SUPPORTED_ARRAY_TYPES,String}

Expand Down Expand Up @@ -150,6 +151,7 @@ const TASK_SCOPE_NAMES = CNPreferences.TASK_SCOPE_NAMES

# NDArray internal
include("ndarray/detail/ndarray.jl")
include("ndarray/detail/linalg.jl")

# Utilities
include("cuda/strided_device_array.jl")
Expand Down
7 changes: 0 additions & 7 deletions src/cuda/cuda_ptx_task.jl
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,6 @@ function check_sz(arr, maxshape)
end
end

# Unused by Launch (which uses `_add_task_array!` + eager finalize). If revived,
# callers must finalize the returned LogicalArray's handle after add_input/output.
function nda_to_logical_array(arr::NDArray{T,N}) where {T,N}
st_handle = cuNumeric.get_store(arr)
return Legate.LogicalArray{T,N}(st_handle, size(arr))
end

# `get_store` returns a Julia-owned `LogicalArrayImplAllocated` that shares the
# underlying Legate store with the NDArray. `add_input`/`add_output` copy that
# array into the task; if we leave the temporary alive until GC, store refcounts
Expand Down
2 changes: 1 addition & 1 deletion src/cuda/cuda_util.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
11 changes: 6 additions & 5 deletions src/cuda/strided_device_array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down
8 changes: 1 addition & 7 deletions src/memory.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,6 @@ const post_gc_host_bytes = Atomic{Int64}(0)
# how much new memory must accumulate before GC fires again
const gc_hysteresis_frac = Ref{Float64}(0.05)

# memory measured right after the last GC
const post_gc_device_bytes = Atomic{Int64}(0)
const post_gc_host_bytes = Atomic{Int64}(0)
# how much new memory must accumulate before GC fires again
const gc_hysteresis_frac = Ref{Float64}(0.05)

@doc"""
init_gc!()

Expand All @@ -38,7 +32,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"""
Expand Down
Loading
Loading