diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index b5a6a7ee5..ebb6c28d1 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -2,7 +2,7 @@ steps: - label: "Documentation" plugins: - JuliaCI/julia#v1: - version: "1.10" + version: "1.12" - sv-oss/node-n#v0.1.2: node-version: v20 command: | @@ -18,7 +18,7 @@ steps: Pkg.build() Pkg.activate("docs") - Pkg.add(; path=".") + Pkg.develop(PackageSpec(path=".")) Pkg.instantiate() push!(LOAD_PATH, @__DIR__) diff --git a/docs/make.jl b/docs/make.jl index 12333ff78..a5f03f859 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -32,15 +32,23 @@ function main() "Home" => "index.md", "Tutorials" => [ "Quick Start" => "tutorials/quickstart.md", + "Custom Structs in Kernels" => "tutorials/custom_structs.md", "Performance Tips" => "tutorials/perf.md", "Profiling" => "tutorials/profiling.md", "Installation Tips" => "install_tips.md", "Testing" => "testing.md", ], + "Usage" => [ + "Array Programming" => "usage/arrays.md", + "Tasks and Streams" => "usage/multitasking.md", + "KernelAbstractions" => "usage/kernelabstractions.md", + ], "API" => [ "Devices" => "api/devices.md", + "System and Configuration" => "api/system.md", "Streams" => "api/streams.md", "Kernel Programming" => "api/kernel_programming.md", + "Reflection" => "api/reflection.md", "Graphs" => "api/graphs.md", "Exceptions" => "api/exceptions.md", "Memory" => "api/memory.md", @@ -48,6 +56,14 @@ function main() "Printing" => "api/printing.md", "Intrinsics" => "api/intrinsics.md", ], + "Libraries" => [ + "Linear Algebra" => "libraries/linalg.md", + "Sparse Arrays" => "libraries/sparse.md", + "Fourier Transforms" => "libraries/fft.md", + "Random Numbers" => "libraries/rand.md", + "Deep Learning (MIOpen)" => "libraries/dnn.md", + ], + "FAQ" => "faq.md", ], doctest=true, warnonly=[:missing_docs], diff --git a/docs/src/api/reflection.md b/docs/src/api/reflection.md new file mode 100644 index 000000000..5e2696126 --- /dev/null +++ b/docs/src/api/reflection.md @@ -0,0 +1,28 @@ +# Introspection and Reflection + +AMDGPU.jl mirrors Julia's `code_*` reflection tools for GPU kernels, letting you inspect what a kernel lowers to at each compilation stage. This is the main way to debug performance and codegen issues — for example checking that a kernel stays type-stable or that a hot loop vectorizes. + +Each `@device_code_*` macro wraps a kernel launch (`@roc ...`) and prints the corresponding representation instead of running it: + +- `@device_code_lowered` — lowered Julia IR. +- `@device_code_typed` — type-inferred Julia IR (use this to spot type instabilities). +- `@device_code_warntype` — type-inferred IR with instabilities highlighted. +- `@device_code_llvm` — the generated LLVM IR. +- `@device_code_gcn` — the final AMD GCN assembly. +- `@device_code` — dump all of the above into a directory. + +```julia +using AMDGPU + +function vadd!(c, a, b) + i = workitemIdx().x + c[i] = a[i] + b[i] + return +end + +a = AMDGPU.ones(Float32, 16); b = AMDGPU.ones(Float32, 16); c = similar(a) + +@device_code_warntype @roc groupsize=16 vadd!(c, a, b) +``` + +The non-macro forms (`AMDGPU.code_typed`, `AMDGPU.code_llvm`, `AMDGPU.code_gcn`, …) take a function and a tuple of argument types directly, mirroring `Base.code_typed` and friends. These reflection tools are inherited from [GPUCompiler.jl](https://github.com/JuliaGPU/GPUCompiler.jl); see its documentation for the full set of options each macro accepts. diff --git a/docs/src/api/system.md b/docs/src/api/system.md new file mode 100644 index 000000000..fd8474850 --- /dev/null +++ b/docs/src/api/system.md @@ -0,0 +1,26 @@ +# System and Configuration + +## Checking the installation + +`AMDGPU.versioninfo()` prints a full report of detected ROCm libraries, their versions, and the available devices. `AMDGPU.functional()` returns whether AMDGPU.jl can run at all, and `AMDGPU.functional(component)` queries an individual ROCm component (see [FAQ](@ref) for the component list). `has_rocm_gpu()` additionally requires that a GPU device is present. + +```@docs +AMDGPU.versioninfo +AMDGPU.functional +AMDGPU.has_rocm_gpu +``` + +## Configuration preferences + +Several behaviours are configured through [Preferences.jl](https://github.com/JuliaPackaging/Preferences.jl) and persist across sessions (they are written to your project's `LocalPreferences.toml`): + +| Preference | Set via | Effect | +|:--|:--|:--| +| `nonblocking_synchronize` | preference | Use non-blocking stream synchronization (default `true`); disable for slightly lower latency. See [Streams](@ref). | +| `eager_gc` | `AMDGPU.eager_gc!(::Bool)` | Trigger GC before allocations under memory pressure. See [Memory Allocation and Intrinsics](@ref). | +| `hard_memory_limit` | `AMDGPU.hard_memory_limit!("8 GiB")` | Hard cap on GPU memory, checked before every allocation. | +| `soft_memory_limit` | `AMDGPU.soft_memory_limit!("6 GiB")` | Advisory limit for the memory pool. | + +## Debugging kernel launches + +Set the environment variable `HIP_LAUNCH_BLOCKING=1` (or toggle `AMDGPU.LAUNCH_BLOCKING[] = true` at runtime) to make kernel launches synchronous. This makes errors surface at the offending launch rather than at a later synchronization point, which is invaluable when tracking down a crashing or misbehaving kernel. diff --git a/docs/src/faq.md b/docs/src/faq.md new file mode 100644 index 000000000..6af6ff262 --- /dev/null +++ b/docs/src/faq.md @@ -0,0 +1,57 @@ +# FAQ + +## How do I check that AMDGPU.jl works? + +`AMDGPU.functional()` returns `true` when the ROCm stack is available and a GPU can be used. For a full report of detected devices, libraries and versions, use `AMDGPU.versioninfo()`. + +```julia +using AMDGPU +AMDGPU.functional() # true if AMDGPU.jl can run on this machine +AMDGPU.versioninfo() # detailed diagnostics +``` + +## How should a package depend on AMDGPU.jl? + +AMDGPU.jl loads on any machine, but only works when ROCm and a supported GPU are present. Code that should run with or without a GPU must therefore guard GPU use behind `AMDGPU.functional()` rather than assume it — importing the package is not enough. + +```julia +using AMDGPU + +if AMDGPU.functional() + x = AMDGPU.ones(Float32, 1024) # run on the GPU +else + x = ones(Float32, 1024) # CPU fallback +end +``` + +For a hard requirement of GPU hardware specifically, `has_rocm_gpu()` additionally checks that at least one device is present. For a heavier optional dependency, prefer a [package extension](https://pkgdocs.julialang.org/v1/creating-packages/#Conditional-loading-of-code-in-packages-(Extensions)) that loads only when AMDGPU is available, following the pattern used by the wider Julia GPU ecosystem. + +## Which ROCm libraries are available? + +Individual components are queried with `AMDGPU.functional(component)`, useful when a feature depends on a specific library: + +```julia +AMDGPU.functional(:rocblas) # dense linear algebra (rocBLAS) +AMDGPU.functional(:rocsolver) # factorizations (rocSOLVER) +AMDGPU.functional(:rocsparse) # sparse arrays (rocSPARSE) +AMDGPU.functional(:rocfft) # FFTs (rocFFT) +AMDGPU.functional(:rocrand) # random numbers (rocRAND) +AMDGPU.functional(:MIOpen) # deep-learning primitives (MIOpen) +AMDGPU.functional(:all) # true only if every component is available +``` + +## My GPU is not detected or a library is missing + +Run `AMDGPU.versioninfo()` and check that `hip` and the library you need report as functional. Missing components usually mean the corresponding ROCm package is not installed. See [Installation Info](@ref) for platform-specific setup, including the package list for distributions such as Fedora. + +## I'm on Arch Linux and ROCm isn't working + +For the last few ROCm releases, users have reported problems with the distro-provided ROCm builds and associated tools ([#770](https://github.com/JuliaGPU/AMDGPU.jl/issues/770), [#696](https://github.com/JuliaGPU/AMDGPU.jl/issues/696), [#767](https://github.com/JuliaGPU/AMDGPU.jl/issues/767)). Some have had success with the [`opencl-amd-dev`](https://aur.archlinux.org/packages/opencl-amd-dev) AUR package instead. + +## How do I control GPU memory usage? + +`ROCArray`s are managed by Julia's garbage collector, and a HIP memory pool caches freed allocations. You can free eagerly, cap usage, and query current usage — see the [Memory Allocation and Intrinsics](@ref) page for `AMDGPU.unsafe_free!`, memory limits, and the caching allocator. + +## Where can I get help? + +Ask on the [Julia Discourse](https://discourse.julialang.org/c/domain/gpu) GPU domain or the `#gpu` channel of the [Julia Slack](https://julialang.org/community/). Bug reports and feature requests are welcome on the [issue tracker](https://github.com/JuliaGPU/AMDGPU.jl/issues). diff --git a/docs/src/index.md b/docs/src/index.md index dd1295d1e..3da8e1e9d 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -93,6 +93,19 @@ gridsize = cld(length(c), groupsize) @assert (a .+ b) ≈ c ``` +## Feature overview + +| Area | What you get | Learn more | +|:--|:--|:--| +| Arrays | Dense `ROCArray`, broadcasting, reductions, sorting | [Array Programming](usage/arrays.md) | +| Kernels | Native `@roc` kernels and portable [KernelAbstractions](usage/kernelabstractions.md) | [Kernel Programming](api/kernel_programming.md) | +| Multi-GPU | Task-local devices and streams | [Tasks and Streams](usage/multitasking.md) | +| Linear algebra | `*`, `\`, `cholesky`, `lu`, `qr` via rocBLAS/rocSOLVER | [Linear Algebra](libraries/linalg.md) | +| Sparse | CSR/CSC/COO arrays, SpMV/SpMM via rocSPARSE | [Sparse Arrays](libraries/sparse.md) | +| FFT | `fft`/`plan_fft` via rocFFT | [Fourier Transforms](libraries/fft.md) | +| Random | `rand`/`randn` via rocRAND | [Random Numbers](libraries/rand.md) | +| Deep learning | Conv/pooling/softmax via MIOpen (NNlib/Flux) | [Deep Learning (MIOpen)](libraries/dnn.md) | + ## Questions and Contributions Usage questions can be posted on the diff --git a/docs/src/install_tips.md b/docs/src/install_tips.md index 94092f295..dadd84a68 100644 --- a/docs/src/install_tips.md +++ b/docs/src/install_tips.md @@ -103,16 +103,3 @@ kernel might throw an exception, specifically during conversions, like: To avoid this, use 'unsafe' conversion option: `unsafe_trunc(Int32, 1f0)`. - -## Frequently-Asked-Questions - -### Archlinux - -For the last few ROCM releases we have seen folks run into -issue with the distro-provided builds of ROCM and associated tools. -[#770](https://github.com/JuliaGPU/AMDGPU.jl/issues/770), -[#696](https://github.com/JuliaGPU/AMDGPU.jl/issues/696), -[#767](https://github.com/JuliaGPU/AMDGPU.jl/issues/767) - -Some users have reported success with using the -[`opencl-amd-dev`](https://aur.archlinux.org/packages/opencl-amd-dev) AUR package. diff --git a/docs/src/libraries/dnn.md b/docs/src/libraries/dnn.md new file mode 100644 index 000000000..2b0a973db --- /dev/null +++ b/docs/src/libraries/dnn.md @@ -0,0 +1,14 @@ +# Deep Learning (MIOpen) + +AMDGPU.jl exposes AMD's [MIOpen](https://github.com/ROCm/MIOpen) deep-learning primitives through the `AMDGPU.MIOpen` submodule: convolutions, pooling, softmax, batch normalization, and activation functions, all operating on `ROCArray`. + +In practice you rarely call these directly. They back the GPU implementations used by [NNlib.jl](https://github.com/FluxML/NNlib.jl) and [Flux.jl](https://github.com/FluxML/Flux.jl), so building and training a model on an AMD GPU with Flux uses MIOpen automatically once your data and parameters are `ROCArray`s. + +Check that MIOpen is available with: + +```julia +using AMDGPU +AMDGPU.functional(:MIOpen) +``` + +If it reports `false`, install the MIOpen ROCm package for your platform (see [Installation Info](@ref)). diff --git a/docs/src/libraries/fft.md b/docs/src/libraries/fft.md new file mode 100644 index 000000000..055865c62 --- /dev/null +++ b/docs/src/libraries/fft.md @@ -0,0 +1,35 @@ +# Fourier Transforms + +AMDGPU.jl implements the [AbstractFFTs.jl](https://github.com/JuliaMath/AbstractFFTs.jl) interface on top of AMD's [rocFFT](https://github.com/ROCm/rocFFT), so `fft`, `ifft`, `rfft`, and the planning API work on `ROCArray` just as they do on the CPU. Load `AbstractFFTs` (or a package that re-exports it, such as `FFTW`) alongside AMDGPU: + +```julia +using AMDGPU +using AbstractFFTs + +x = ROCArray(ComplexF32[1, 2, 3, 4]) +y = fft(x) # forward transform on the GPU +z = ifft(y) # z ≈ x +``` + +Real-to-complex transforms are available through `rfft`: + +```julia +xr = AMDGPU.rand(Float32, 1024) +yr = rfft(xr) # length 513 complex output +``` + +## Plans + +For repeated transforms of the same size, create a plan once and reuse it to amortize setup cost. Both out-of-place and in-place plans are supported: + +```julia +x = ROCArray(rand(ComplexF32, 1024)) + +p = plan_fft(x) # out-of-place +y = p * x + +p! = plan_fft!(x) # in-place, overwrites its argument +p! * x +``` + +Multidimensional transforms and transforms over selected dimensions work as usual, e.g. `fft(x, (1, 2))` for a 2D transform of a higher-dimensional array. diff --git a/docs/src/libraries/linalg.md b/docs/src/libraries/linalg.md new file mode 100644 index 000000000..24eb3a00f --- /dev/null +++ b/docs/src/libraries/linalg.md @@ -0,0 +1,53 @@ +```@meta +DocTestSetup = quote + using AMDGPU + using LinearAlgebra +end +``` + +# Linear Algebra + +Standard `LinearAlgebra` functions work directly on `ROCArray`, dispatching to AMD's [rocBLAS](https://github.com/ROCm/rocBLAS) (dense BLAS) and [rocSOLVER](https://github.com/ROCm/rocSOLVER) (LAPACK-style factorizations). You write ordinary Julia linear algebra; the GPU libraries are used automatically. + +Supported out of the box on `ROCArray`: + +- Matrix–matrix and matrix–vector products (`*`, `mul!`), including mixed- and low-precision (`Float16`) matmul. +- BLAS level-1 operations: `dot`, `norm`, `axpy!`, `axpby!`, `rmul!`. +- Triangular solves and `\` (linear systems, LU-based). +- Factorizations: `cholesky`, `lu`, `qr`, `svd`. + +```jldoctest linalg +julia> A = ROCArray(Float32[4 1; 1 3]); + +julia> b = ROCArray(Float32[1, 2]); + +julia> x = A \ b; # solve, LU via rocSOLVER + +julia> Array(A * x) ≈ Array(b) +true + +julia> C = cholesky(A); # Cholesky factorization + +julia> Array(C.U' * C.U) ≈ Array(A) +true +``` + +Element types follow rocBLAS/rocSOLVER: `Float32`, `Float64`, `ComplexF32`, and `ComplexF64` (with `Float16` also supported for matmul). + +## Singular value decomposition + +`svd`, `svd!`, and `svdvals` run on `ROCMatrix` via rocSOLVER. The `alg` keyword selects the backend — `JacobiAlgorithm()` (the default, usually fastest on AMD GPUs) or `QRAlgorithm()`: + +```julia +using AMDGPU.rocSOLVER: JacobiAlgorithm, QRAlgorithm + +F = svd(A) # Jacobi by default; F.U, F.S, F.Vt +s = svdvals(A) # singular values only +Ff = svd(A; full = true, alg = QRAlgorithm()) +``` + +Because `svdvals` is supported, so is `cond`. (`opnorm(A, 2)` is not yet available — it relies on scalar indexing internal to `LinearAlgebra`.) + +## Direct library access + +Lower-level routines are available in the `AMDGPU.rocBLAS` and `AMDGPU.rocSOLVER` submodules for cases the generic interface does not cover — for example batched factorizations, or the raw `gesvd!` (QR) and `gesvdj!` (Jacobi) SVD kernels. diff --git a/docs/src/libraries/rand.md b/docs/src/libraries/rand.md new file mode 100644 index 000000000..dc989fec4 --- /dev/null +++ b/docs/src/libraries/rand.md @@ -0,0 +1,31 @@ +# Random Numbers + +AMDGPU.jl generates random numbers on the device through AMD's [rocRAND](https://github.com/ROCm/rocRAND). The quickest way to get a random `ROCArray` is the package-level `rand`/`randn`, which mirror `Base`: + +```julia +using AMDGPU + +AMDGPU.rand(Float32, 1024) # uniform in [0, 1) +AMDGPU.randn(Float32, 8, 8) # standard normal +AMDGPU.rand(Int32, 100) # uniform integers +``` + +The standard `Random` in-place interface works on existing `ROCArray`s and fills them on the GPU: + +```julia +using Random + +a = ROCArray{Float32}(undef, 1024) +Random.rand!(a) +Random.randn!(a) +``` + +## Seeding + +Seed the device generators for reproducible results with `AMDGPU.seed!`: + +```julia +AMDGPU.seed!(1234) +``` + +This seeds both the rocRAND generator (used by `AMDGPU.rand`/`randn`) and the GPUArrays fallback generator, so results are reproducible regardless of which path a given operation takes. diff --git a/docs/src/libraries/sparse.md b/docs/src/libraries/sparse.md new file mode 100644 index 000000000..d27e3cdbd --- /dev/null +++ b/docs/src/libraries/sparse.md @@ -0,0 +1,34 @@ +# Sparse Arrays + +AMDGPU.jl wraps AMD's [rocSPARSE](https://github.com/ROCm/rocSPARSE) and stores sparse matrices on the device with the `AMDGPU.rocSPARSE` array types: + +- `ROCSparseMatrixCSR` — compressed sparse row (the usual default for GPU work). +- `ROCSparseMatrixCSC` — compressed sparse column. +- `ROCSparseMatrixCOO` — coordinate format. +- `ROCSparseVector` — sparse vector. + +Construct them by converting a host `SparseMatrixCSC`, and convert back the same way: + +```julia +using AMDGPU, SparseArrays +using AMDGPU.rocSPARSE + +S = sprand(Float32, 1000, 1000, 0.01) +dS = ROCSparseMatrixCSR(S) # upload to the GPU + +SparseMatrixCSC(dS) # download back to the host +``` + +## Operations + +Sparse matrix–vector (SpMV) and matrix–matrix (SpMM) products use the standard `*` operator: + +```julia +x = AMDGPU.rand(Float32, 1000) +y = dS * x # SpMV + +B = AMDGPU.rand(Float32, 1000, 8) +Y = dS * B # SpMM +``` + +Incomplete-factorization preconditioners (`ic0`, `ilu0`) and further sparse routines (conversions between formats, triangular solves, gather/scatter) are available in the `AMDGPU.rocSPARSE` submodule for building iterative solvers on the GPU. diff --git a/docs/src/testing.md b/docs/src/testing.md index b7c45e91e..f08271655 100644 --- a/docs/src/testing.md +++ b/docs/src/testing.md @@ -40,3 +40,18 @@ julia> Pkg.test("AMDGPU"; test_args=`gpuarrays`) !!! warning "Large memory tests" Some tests such as HIP and GPUArrays tests may use > 20GB of host RAM. It is recommended to use fewer workers (<= 4) on machines that have < 32Gb of host RAM in case running tests would result in out of memory errors. + +## Building the documentation + +The documentation is built with [Documenter.jl](https://documenter.juliadocs.org) and [DocumenterVitepress.jl](https://github.com/LuxDL/DocumenterVitepress.jl). To build it locally: + +``` +julia --project=docs -e 'using Pkg; Pkg.instantiate()' +julia --project=docs docs/make.jl +julia --project=docs -e 'using LiveServer; serve(dir="docs/build/1")' +``` + +The last command serves the built site locally; open the printed URL in a browser. + +!!! note "Doctests need a GPU" + `make.jl` runs with `doctest=true`, so every ```` ```jldoctest ```` block is executed on a real device. Building the docs therefore requires a functional AMD GPU, and a clean build means all examples still produce their documented output. When writing examples, prefer showing `Array(x)` rather than a raw `ROCArray` so the output does not depend on internal buffer types. diff --git a/docs/src/tutorials/custom_structs.md b/docs/src/tutorials/custom_structs.md new file mode 100644 index 000000000..0069d0b89 --- /dev/null +++ b/docs/src/tutorials/custom_structs.md @@ -0,0 +1,33 @@ +# Using Custom Structs in Kernels + +Kernels often need more than plain arrays — you may want to pass your own struct bundling several arrays and parameters. The difficulty is that a `ROCArray` lives in host memory management, but inside a kernel it must appear as a device array (`ROCDeviceArray`). AMDGPU.jl handles this conversion automatically for arguments to `@roc`, using [Adapt.jl](https://github.com/JuliaGPU/Adapt.jl) to reach *inside* your struct and convert its array fields. + +Declare how your struct should be adapted with `Adapt.@adapt_structure`, then pass an instance to a kernel like any other argument: + +```julia +using AMDGPU, Adapt + +struct Model{A} + weights::A + scale::Float32 +end +Adapt.@adapt_structure Model + +function apply!(out, m) + i = workitemIdx().x + (workgroupIdx().x - 1) * workgroupDim().x + if i <= length(out) + out[i] = m.weights[i] * m.scale + end + return +end + +w = AMDGPU.ones(Float32, 16) +m = Model(w, 3f0) +out = AMDGPU.zeros(Float32, 16) + +@roc groupsize=16 apply!(out, m) # out .== 3.0 +``` + +`@adapt_structure` generates the rule that rebuilds `Model` with each field adapted. When `@roc` launches the kernel it calls `AMDGPU.rocconvert` on `m`, which turns the `weights::ROCArray` field into a `ROCDeviceArray` while leaving `scale` untouched. Inside the kernel `m.weights` is therefore a device array you can index directly. + +The type parameter `A` matters: it lets the struct hold a `ROCArray` on the host and a `ROCDeviceArray` on the device without you writing two types. Keep the remaining fields `isbits` (numbers, tuples, other adapted structs) so the whole struct can be passed to the GPU. diff --git a/docs/src/usage/arrays.md b/docs/src/usage/arrays.md new file mode 100644 index 000000000..a7c22f4c9 --- /dev/null +++ b/docs/src/usage/arrays.md @@ -0,0 +1,142 @@ +```@meta +DocTestSetup = quote + using AMDGPU +end +``` + +# Array Programming + +The `ROCArray` type is the core of AMDGPU.jl. It is a dense, GPU-resident array that implements Julia's `AbstractArray` interface, so most array code written for `Array` works on the GPU with little or no change. This is the recommended, high-level way to use AMDGPU.jl — you rarely need to write kernels by hand. + +## Construction + +Copy an existing array to the device by wrapping it in a `ROCArray`, or build one directly on the device with the familiar constructors: + +```jldoctest arrays +julia> ROCArray([1, 2, 3, 4]) |> Array # move host array to device (shown via Array) +4-element Vector{Int64}: + 1 + 2 + 3 + 4 + +julia> AMDGPU.ones(Float32, 2, 2) |> Array +2×2 Matrix{Float32}: + 1.0 1.0 + 1.0 1.0 +``` + +The device-side constructors mirror `Base`: `AMDGPU.zeros`, `AMDGPU.ones`, `AMDGPU.fill`, `AMDGPU.rand`, `AMDGPU.randn`, and `similar`. All take an optional element type and dimensions. + +Alternatively, `roc` copies to the device while narrowing floating-point element types to 32-bit — handy when single precision is preferred for performance. It also recurses into custom structs, converting their array fields: + +```jldoctest arrays +julia> x = roc([1.0, 2.0, 3.0]); # Float64 host array + +julia> eltype(x) # narrowed to Float32 on the device +Float32 + +julia> x isa ROCArray +true +``` + +```@docs +AMDGPU.ROCArray +AMDGPU.roc +``` + +## Host ↔ device transfer + +Wrapping a host array in `ROCArray` copies it to the GPU; calling `Array` on a `ROCArray` copies it back: + +```jldoctest arrays +julia> a = ROCArray(Float32[1, 2, 3]); + +julia> Array(a) +3-element Vector{Float32}: + 1.0 + 2.0 + 3.0 +``` + +Transfers are relatively expensive — keep data on the device across as many operations as possible rather than copying back and forth. + +## Broadcasting + +Broadcasting works exactly as on the CPU and fuses into a single kernel, so chained element-wise operations do not allocate intermediate arrays: + +```jldoctest arrays +julia> x = ROCArray(Float32[1, 2, 3]); + +julia> y = ROCArray(Float32[10, 20, 30]); + +julia> Array(@. 2x + y) +3-element Vector{Float32}: + 12.0 + 24.0 + 36.0 +``` + +## Reductions, scans and sorting + +Reductions (`sum`, `prod`, `maximum`, `mapreduce`, …), scans (`cumsum`, `accumulate`), `sort`/`sortperm`, and `reverse` all run on the GPU: + +```jldoctest arrays +julia> sum(ROCArray(1:100)) +5050 + +julia> mapreduce(x -> x^2, +, ROCArray(1:4)) +30 + +julia> Array(cumsum(ROCArray([1, 2, 3, 4]))) +4-element Vector{Int64}: + 1 + 3 + 6 + 10 + +julia> Array(sort(ROCArray([3, 1, 2, 5, 4]))) +5-element Vector{Int64}: + 1 + 2 + 3 + 4 + 5 +``` + +## Scalar indexing + +Reading or writing a single element from the host (`a[i]`) would require a separate GPU transfer per element, which is catastrophically slow. AMDGPU.jl therefore *disallows* scalar indexing by default and throws an error: + +```julia +julia> a = ROCArray([1, 2, 3]); + +julia> a[1] +ERROR: Scalar indexing is disallowed. +``` + +If you genuinely need it (e.g. in a one-off test), opt in explicitly: + +```julia +AMDGPU.allowscalar(true) # globally (discouraged) +AMDGPU.@allowscalar a[1] # for a single expression (preferred) +``` + +Prefer vectorized operations, broadcasting, or a custom kernel instead. See [Kernel Programming](@ref) for writing your own kernels. + +## Views and reshaping + +`view`, `reshape`, `reinterpret`, and adjoint/transpose all work and return lazy wrappers that share the parent's memory — no copy is made: + +```jldoctest arrays +julia> a = ROCArray(reshape(1:6, 2, 3)); + +julia> Array(view(a, :, 2)) +2-element Vector{Int64}: + 3 + 4 +``` + +## Linear algebra + +Matrix multiplication and factorizations dispatch to the vendor libraries (rocBLAS, rocSOLVER). Standard `LinearAlgebra` functions — `*`, `mul!`, `\`, `cholesky`, `lu`, `qr` — work directly on `ROCArray`. See [Linear Algebra](@ref) for the list of accelerated operations. diff --git a/docs/src/usage/kernelabstractions.md b/docs/src/usage/kernelabstractions.md new file mode 100644 index 000000000..15aeac1cb --- /dev/null +++ b/docs/src/usage/kernelabstractions.md @@ -0,0 +1,38 @@ +# KernelAbstractions + +[KernelAbstractions.jl](https://github.com/JuliaGPU/KernelAbstractions.jl) lets you write a single kernel that runs on CPUs and on all supported GPU backends. AMDGPU.jl provides the `ROCBackend` for it, so KernelAbstractions is the recommended way to write **portable** kernels — code that works unchanged on AMDGPU, CUDA, and others. + +KernelAbstractions is a separate package; add it alongside AMDGPU: + +```julia +using Pkg +Pkg.add("KernelAbstractions") +``` + +Write kernels with the `@kernel` macro and index them with `@index` instead of the AMDGPU-specific `workitemIdx`/`workgroupIdx` intrinsics: + +```julia +using AMDGPU +using KernelAbstractions + +@kernel function vadd!(c, a, b) + i = @index(Global) + c[i] = a[i] + b[i] +end + +a = AMDGPU.ones(Float32, 1024) +b = AMDGPU.ones(Float32, 1024) +c = similar(a) + +backend = ROCBackend() +vadd!(backend, 256)(c, a, b; ndrange = length(c)) +KernelAbstractions.synchronize(backend) +``` + +`ROCBackend()` selects AMDGPU as the execution backend. The `256` is the workgroup size, and `ndrange` is the total number of work items. `get_backend(::ROCArray)` returns a `ROCBackend`, so generic code can obtain the right backend directly from its arrays. + +For the kernel-authoring API (`@kernel`, `@index`, `@localmem`, `@synchronize`, …), see the [KernelAbstractions documentation](https://juliagpu.github.io/KernelAbstractions.jl/stable/). To write AMDGPU-specific kernels with the native intrinsics instead, see [Kernel Programming](@ref). + +```@docs +AMDGPU.ROCBackend +``` diff --git a/docs/src/usage/multitasking.md b/docs/src/usage/multitasking.md new file mode 100644 index 000000000..7354f0df8 --- /dev/null +++ b/docs/src/usage/multitasking.md @@ -0,0 +1,53 @@ +# Tasks and Streams + +AMDGPU.jl follows a **task-local** execution model: the active device and stream are properties of the current Julia task. Switching either one affects only the task that makes the switch, so concurrent tasks can drive different devices or streams without interfering with each other. This makes ordinary Julia task-based parallelism the natural way to overlap work and to use several GPUs at once. + +## Selecting a device + +All supported GPUs are auto-detected. The device bound to the current task is returned by `AMDGPU.device()`, and `AMDGPU.devices()` lists all of them. Switch the current task's device with `AMDGPU.device!`: + +```julia +AMDGPU.devices() # all detected devices +AMDGPU.device() # device of the current task + +AMDGPU.device!(AMDGPU.devices()[2]) # switch this task to the second device +x = AMDGPU.ones(Float32, 16) # allocated on the second device +``` + +Arrays and kernels use the current task's device, so allocate and launch after switching. See [Devices](@ref) for the full reference and device-property queries. + +## Streams and asynchrony + +Kernel launches and most operations are asynchronous with respect to the host: they are enqueued on a HIP stream and return immediately. Work on the same stream runs in order; work on different streams may overlap. Each task has a default stream, and you pick a stream per launch or per task: + +```julia +stream = AMDGPU.HIPStream() +@roc stream=stream kernel(args...) # launch on a specific stream +AMDGPU.stream!(stream) # or make it the task default +``` + +Because launches are asynchronous, synchronize before reading results back or timing code. `AMDGPU.synchronize()` waits for the current stream, `AMDGPU.@sync` wraps an expression and synchronizes after it, and `AMDGPU.device_synchronize()` waits for the whole device: + +```julia +AMDGPU.@sync @roc groupsize=256 gridsize=n kernel(args...) +``` + +Streams also carry a priority (`:normal`, `:low`, `:high`) to bias scheduling. See [Streams](@ref) for stream priorities, synchronization details, and the blocking-vs-nonblocking preference. + +## Using multiple GPUs + +Because device selection is task-local, drive several GPUs by spawning one task per device. Each task switches to its device, then allocates and launches there: + +```julia +devs = AMDGPU.devices() +@sync for (i, dev) in enumerate(devs) + Threads.@spawn begin + AMDGPU.device!(dev) + a = AMDGPU.ones(Float32, 1024) + b = a .+ i + AMDGPU.synchronize() + end +end +``` + +Data does not move between devices automatically. To share results, copy through the host (`Array(x)` on the source device, then `ROCArray` on the destination) or use peer-to-peer transfers where supported. diff --git a/src/ROCKernels.jl b/src/ROCKernels.jl index 5da8810e1..bd710631a 100644 --- a/src/ROCKernels.jl +++ b/src/ROCKernels.jl @@ -12,6 +12,13 @@ import LLVM using StaticArraysCore: MArray +""" + ROCBackend <: KernelAbstractions.GPU + +KernelAbstractions backend that executes kernels on an AMD GPU via AMDGPU.jl. +Pass `ROCBackend()` to a KernelAbstractions kernel to run it on the GPU, or +obtain it from an array with `KernelAbstractions.get_backend(::ROCArray)`. +""" struct ROCBackend <: KA.GPU end KA.functional(::ROCBackend) = AMDGPU.functional() diff --git a/src/array.jl b/src/array.jl index 451d67fa5..351a7b111 100644 --- a/src/array.jl +++ b/src/array.jl @@ -1,3 +1,21 @@ +""" + ROCArray{T,N,B} <: AbstractGPUArray{T,N} + +`N`-dimensional dense array of element type `T` stored in GPU memory (backed by +buffer type `B`). `ROCArray` implements Julia's `AbstractArray` interface, so +broadcasting, reductions, and linear algebra run on the GPU. + +Copy a host array to the device by wrapping it, or allocate directly: + +```julia +ROCArray([1, 2, 3]) # copy a host array to the device +ROCArray{Float32}(undef, 4, 4) # uninitialized 4×4 device matrix +``` + +Move data back to the host with `Array(x)`. See also [`roc`](@ref), which +copies to the device while narrowing floating-point types to 32-bit, and the +`AMDGPU.zeros` / `AMDGPU.ones` / `AMDGPU.rand` constructors. +""" mutable struct ROCArray{T, N, B} <: AbstractGPUArray{T, N} buf::DataRef{Managed{B}} dims::Dims{N} @@ -295,6 +313,24 @@ Adapt.adapt_storage(::Float32Adaptor, xs::AbstractArray{<:Complex{<:AbstractFloa Adapt.adapt_storage(::Float32Adaptor, xs::AbstractArray{Float16}) = isbits(xs) ? xs : convert(ROCArray, xs) +""" + roc(x) + +Adapt `x` for the GPU: convert arrays to [`ROCArray`](@ref) while **narrowing +floating-point element types to 32-bit** (`Float64`→`Float32`, +`ComplexF64`→`ComplexF32`; `Float16` is left unchanged, other element types are +preserved). Like `Adapt.adapt`, it recurses into custom structs and converts +their array fields. + +This mirrors CUDA.jl's `cu`. Reach for it when single precision is preferred +(e.g. for performance); use the [`ROCArray`](@ref) constructor directly to keep +the original element type. + +```julia +roc([1.0, 2.0]) # 2-element ROCArray{Float32} +roc(1:3) # non-float eltype preserved: ROCArray{Int64} +``` +""" roc(xs) = adapt(Float32Adaptor(), xs) Base.unsafe_convert(typ::Type{Ptr{T}}, x::ROCArray{T}) where T = diff --git a/src/utils.jl b/src/utils.jl index 4fab0464f..8c6cbfccd 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1,3 +1,10 @@ +""" + versioninfo(io::IO=stdout) + +Print a report of the AMDGPU.jl setup: detected ROCm libraries and their +versions, tool paths, and the available GPU devices. Useful as a first +diagnostic when something is missing or not working. +""" function versioninfo(io::IO=stdout) println(io, "AMDGPU versioninfo") _status(st::Bool) = st ? "+" : "-" @@ -102,6 +109,13 @@ function functional(component::Symbol) end end +""" + has_rocm_gpu() -> Bool + +Return `true` if HIP is functional and at least one GPU device is present. +Use this to guard code that specifically requires GPU hardware; for a general +"can AMDGPU.jl run here" check prefer [`AMDGPU.functional`](@ref). +""" has_rocm_gpu() = functional(:hip) && length(devices()) > 0 function print_build_diagnostics()