Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
10 changes: 8 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ option(AF_BUILD_CPU "Build ArrayFire with a CPU backend" ON)
option(AF_BUILD_CUDA "Build ArrayFire with a CUDA backend" ${CUDA_FOUND})
option(AF_BUILD_OPENCL "Build ArrayFire with a OpenCL backend" ${OpenCL_FOUND})
option(AF_BUILD_ONEAPI "Build ArrayFire with a oneAPI backend" OFF)
option(AF_BUILD_METAL "Build ArrayFire with a Metal backend" OFF)
option(AF_BUILD_UNIFIED "Build Backend-Independent ArrayFire API" ON)
option(AF_BUILD_DOCS "Create ArrayFire Documentation" ${DOXYGEN_FOUND})
option(AF_BUILD_EXAMPLES "Build Examples" ON)
Expand Down Expand Up @@ -234,7 +235,7 @@ find_package(LAPACK)
#
# Note that the default value of AF_COMPUTE_LIBRARY is Intel-MKL.
# Also, cmake doesn't have short-circuit of OR/AND conditions in if
if(${AF_BUILD_CPU} OR ${AF_BUILD_OPENCL})
if(${AF_BUILD_CPU} OR ${AF_BUILD_OPENCL} OR ${AF_BUILD_METAL})
if("${AF_COMPUTE_LIBRARY}" STREQUAL "Intel-MKL"
OR "${AF_COMPUTE_LIBRARY}" STREQUAL "MKL")
af_mkl_batch_check()
Expand Down Expand Up @@ -386,6 +387,7 @@ conditional_directory(AF_BUILD_CPU src/backend/cpu)
conditional_directory(AF_BUILD_CUDA src/backend/cuda)
conditional_directory(AF_BUILD_ONEAPI src/backend/oneapi)
conditional_directory(AF_BUILD_OPENCL src/backend/opencl)
conditional_directory(AF_BUILD_METAL src/backend/metal)
conditional_directory(AF_BUILD_UNIFIED src/api/unified)

if(TARGET af)
Expand All @@ -408,6 +410,10 @@ if(TARGET afopencl)
list(APPEND built_backends afopencl)
endif()

if(TARGET afmetal)
list(APPEND built_backends afmetal)
endif()

set_target_properties(${built_backends} PROPERTIES
CXX_STANDARD 17
CXX_EXTENSIONS OFF
Expand Down Expand Up @@ -496,7 +502,7 @@ install(DIRECTORY "${ArrayFire_SOURCE_DIR}/LICENSES/"
DESTINATION LICENSES
COMPONENT licenses)

foreach(backend CPU CUDA OpenCL oneAPI Unified)
foreach(backend CPU CUDA OpenCL oneAPI Metal Unified)
string(TOUPPER ${backend} upper_backend)
string(TOLOWER ${backend} lower_backend)
if(AF_BUILD_${upper_backend})
Expand Down
11 changes: 9 additions & 2 deletions CMakeModules/ArrayFireConfig.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
# Target for the ArrayFire oneAPI backend.
# ``ArrayFire::afopencl``
# Target for the ArrayFire OpenCL backend.
# ``ArrayFire::afmetal``
# Target for the ArrayFire Metal backend.
#
# These targets can be used to link with your application using the
# ``target_link_library`` command. Here is an example of how to use these
Expand Down Expand Up @@ -72,6 +74,11 @@
# ``ArrayFire_OpenCL_LIBRARIES``
# Location of ArrayFire's OpenCL library, if found
#
# ``ArrayFire_Metal_FOUND``
# True of the ArrayFire Metal library has been found.
# ``ArrayFire_Metal_LIBRARIES``
# Location of ArrayFire's Metal library, if found
#
# ``ArrayFire_Unified_FOUND``
# True of the ArrayFire Unified library has been found.
# ``ArrayFire_Unified_LIBRARIES``
Expand All @@ -92,7 +99,7 @@

set_and_check(ArrayFire_INCLUDE_DIRS @PACKAGE_INCLUDE_DIRS@)

foreach(backend Unified CPU oneAPI OpenCL CUDA)
foreach(backend Unified CPU oneAPI OpenCL CUDA Metal)
if(backend STREQUAL "Unified")
set(lowerbackend "")
else()
Expand Down Expand Up @@ -147,4 +154,4 @@ foreach(_comp ${ArrayFire_FIND_COMPONENTS})
endif()
endforeach()

check_required_components(CPU oneAPI OpenCL CUDA Unified)
check_required_components(CPU oneAPI OpenCL CUDA Metal Unified)
11 changes: 9 additions & 2 deletions CMakeModules/FileToString.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
set(BIN2CPP_PROGRAM "bin2cpp")

function(FILE_TO_STRING)
cmake_parse_arguments(RTCS "WITH_EXTENSION;NULLTERM" "VARNAME;EXTENSION;OUTPUT_DIR;TARGETS;NAMESPACE;BINARY" "SOURCES" ${ARGN})
cmake_parse_arguments(RTCS "WITH_EXTENSION;NULLTERM;NO_INCLUDE" "VARNAME;EXTENSION;OUTPUT_DIR;TARGETS;NAMESPACE;BINARY" "SOURCES" ${ARGN})

set(_output_files "")
foreach(_input_file ${RTCS_SOURCES})
Expand Down Expand Up @@ -54,11 +54,18 @@ function(FILE_TO_STRING)
set(_output_file "${_output_path}/${_name_we}.${RTCS_EXTENSION}")
endif()

if(RTCS_NO_INCLUDE)
set(_include_command "")
else()
set(_include_command
COMMAND ${CMAKE_COMMAND} -E echo "\\#include \\<${_path}/${_name_we}.hpp\\>" >>"${_output_file}")
endif()

add_custom_command(
OUTPUT ${_output_file}
DEPENDS ${_input_file} ${BIN2CPP_PROGRAM}
COMMAND ${CMAKE_COMMAND} -E make_directory "${_output_path}"
COMMAND ${CMAKE_COMMAND} -E echo "\\#include \\<${_path}/${_name_we}.hpp\\>" >>"${_output_file}"
${_include_command}
COMMAND ${BIN2CPP_PROGRAM} --file ${_name} --namespace ${_namespace} --output ${_output_file} --name ${var_name} ${_binary} ${_nullterm}
WORKING_DIRECTORY "${_path}"
COMMENT "Compiling ${_input_file} to C++ source"
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ Several of ArrayFire's benefits include:
* [Easy to use](http://arrayfire.org/docs/gettingstarted.htm), stable,
[well-documented](http://arrayfire.org/docs) API
* Rigorous benchmarks and tests ensuring top performance and numerical accuracy
* Cross-platform compatibility with support for CUDA, oneAPI, OpenCL, and
native CPU on Windows, Mac, and Linux
* Cross-platform compatibility with support for CUDA, oneAPI, Metal, OpenCL,
and native CPU on Windows, Mac, and Linux
* Built-in visualization functions through
[Forge](https://github.com/arrayfire/forge)
* Commercially friendly open-source licensing
Expand All @@ -36,9 +36,9 @@ translated into near-optimal kernels that execute on the computational device.

ArrayFire runs on devices ranging from low-power mobile phones to high-power
GPU-enabled supercomputers. ArrayFire runs on CPUs from all major vendors
(Intel, AMD, ARM), GPUs from the prominent manufacturers (AMD, Intel, NVIDIA,
and Qualcomm), as well as a variety of other accelerator devices on Windows,
Mac, and Linux.
(Intel, AMD, ARM), GPUs from the prominent manufacturers (AMD, Apple, Intel,
NVIDIA, and Qualcomm), as well as a variety of other accelerator devices on
Windows, Mac, and Linux.

# Getting ArrayFire

Expand Down
9 changes: 5 additions & 4 deletions docs/pages/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ readable math-resembling notation. Expertise in parallel programming _is not_
required to use ArrayFire.

A few lines of ArrayFire code accomplishes what can take 100s of complicated
lines in CUDA, oneAPI, or OpenCL kernels.
lines in CUDA, oneAPI, Metal, or OpenCL kernels.

## ArrayFire is extensive!

Expand Down Expand Up @@ -61,7 +61,7 @@ CUDA, oneAPI, or OpenCL code.

## Code once, run anywhere!

With support for x86, ARM, CUDA, oneAPI, and OpenCL devices, ArrayFire
With support for x86, ARM, CUDA, oneAPI, Metal, and OpenCL devices, ArrayFire
supports for a comprehensive list of devices.

Each ArrayFire installation comes with:
Expand All @@ -71,16 +71,17 @@ Each ArrayFire installation comes with:
devices](https://www.intel.com/content/www/us/en/developer/articles/system-requirements/intel-oneapi-base-toolkit-system-requirements.html),
- an OpenCL backend (named 'libafopencl') for [OpenCL
devices](http://www.khronos.org/conformance/adopters/conformant-products#opencl),
- a Metal backend (named 'libafmetal') for Apple silicon GPUs on macOS,
- a CPU backend (named 'libafcpu') to fall back to when CUDA, oneAPI, or
OpenCL devices are unavailable.
Metal or OpenCL devices are unavailable.

## ArrayFire is highly efficient

#### Vectorized and Batched Operations

ArrayFire supports batched operations on N-dimensional arrays. Batch
operations in ArrayFire are run in parallel ensuring an optimal usage of CUDA,
oneAPI, or OpenCL devices.
oneAPI, Metal, or OpenCL devices.

Best performance with ArrayFire is achieved using
[vectorization techniques](\ref vectorization).
Expand Down
54 changes: 54 additions & 0 deletions docs/pages/metal_backend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
Metal Backend {#metal_backend}
=============

[TOC]

# Supported configuration

The Metal backend provides native GPU execution on macOS. Its initial support
target is macOS 12 or later on Apple silicon (arm64, M1 or newer). The backend
has been validated with a native arm64 Release build on an Apple M4 system.
Intel-based Macs and non-macOS Apple platforms are not currently validated.

The backend uses the system-default Metal device. When both Metal and OpenCL
are installed, the Unified backend selects Metal first. Applications can still
select OpenCL explicitly with af::setBackend(AF_BACKEND_OPENCL).

# Building

Building the Metal backend requires Xcode or the Xcode Command Line Tools, a
C++17 compiler, and Apple's header-only
[metal-cpp](https://developer.apple.com/metal/cpp/) interface. The metal-cpp
headers are not vendored by ArrayFire. Download and extract them from Apple,
then configure ArrayFire with the directory containing `Metal.hpp`:

cmake -S . -B build \
-DAF_BUILD_METAL=ON \
-DCMAKE_OSX_ARCHITECTURES=arm64 \
-DMETALCPP_INCLUDE_DIR=/absolute/path/to/metal-cpp/Metal
cmake --build build

The single-header metal-cpp distribution is also supported by setting
`METALCPP_INCLUDE_DIR` to the directory containing the generated `Metal.hpp`.

Applications can link directly to `ArrayFire::afmetal`, or link to
`ArrayFire::af` and select AF_BACKEND_METAL through the Unified backend.

# Current limitations

The backend implements the ArrayFire API subject to Metal device resource
limits and the following backend-specific constraints:

* Only the system-default Metal device is currently exposed.
* Pinned host-memory allocation is not supported.
* Metal shaders do not provide native double-precision arithmetic. Operations
on double and double-complex arrays use host-side or library fallbacks where
implemented and should not be expected to provide GPU FP64 performance.
* Two-dimensional morphology masks larger than 19 by 19 are not supported for
non-boolean input types.
* Metal kernels are compiled from embedded source when the backend is first
initialized, so the first operation can include pipeline compilation time.

Array sizes and dispatch dimensions must also fit the limits reported by the
active Metal device. Allocation or pipeline failures are reported through the
normal ArrayFire error mechanism.
1 change: 1 addition & 0 deletions docs/pages/tutorials.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* [Array and Matrix Manipulation](\ref matrixmanipulation)
* [CUDA Interoperability](\ref interop_cuda)
* [OpenCL Interoperability](\ref interop_opencl)
* [Metal Backend](\ref metal_backend)
* [Unified Backend](\ref unifiedbackend)
* [Forge Visualization](\ref forge_visualization)
* [Indexing](\ref indexing)
Expand Down
34 changes: 28 additions & 6 deletions docs/pages/unified_backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ Unified Backend {#unifiedbackend}

The Unified backend was introduced in ArrayFire with version 3.2.
While this is not an independent backend, it allows the user to switch between
the different ArrayFire backends (CPU, CUDA, oneAPI and OpenCL) at runtime.
the different ArrayFire backends (CPU, CUDA, oneAPI, Metal and OpenCL) at
runtime.

# Compiling with Unified

Expand All @@ -24,7 +25,7 @@ To use with CMake, use the __ArrayFire_Unified_LIBRARIES__ variable.
# Using the Unified Backend

The Unified backend will try to dynamically load the backend libraries. The
priority of backends is __CUDA -> oneAPI -> OpenCL -> CPU__
priority of backends is __CUDA -> oneAPI -> Metal -> OpenCL -> CPU__.

The most important aspect to note here is that all the libraries the ArrayFire
libs depend on need to be in the environment paths
Expand All @@ -49,6 +50,7 @@ To select a backend, call the af::setBackend function as shown below.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
af::setBackend(AF_BACKEND_CUDA); // Sets CUDA as current backend
af::setBackend(AF_BACKEND_METAL); // Sets Metal as current backend
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

To get the count of the number of backends available (the number of `libaf*`
Expand Down Expand Up @@ -105,6 +107,15 @@ int main()
fprintf(stderr, "%s\n", e.what());
}

try {
printf("Trying Metal Backend\n");
af::setBackend(AF_BACKEND_METAL);
testBackend();
} catch (af::exception& e) {
printf("Caught exception when trying Metal backend\n");
fprintf(stderr, "%s\n", e.what());
}

return 0;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -159,6 +170,17 @@ This output would be:
0.2126 0.7509 0.6450 0.8962
0.0655 0.4105 0.9675 0.3712

Trying Metal Backend
ArrayFire v3.10.0 (Metal, 64-bit Mac OSX)
[0] Apple: Apple M4, 12124 MB, Metal, unified memory
af::randu(5, 4)
[5 4 1 1]
0.6010 0.5497 0.1583 0.3636
0.0278 0.2864 0.3712 0.4165
0.9806 0.3410 0.3543 0.5814
0.2126 0.7509 0.6450 0.8962
0.0655 0.4105 0.9675 0.3712


# Dos and Don'ts

Expand Down Expand Up @@ -196,15 +218,15 @@ int main()
### Do: Use a naming scheme to track arrays and backends

We recommend that you use a technique to track the arrays on the backends. One
suggested technique would be to use a suffix of `_cpu`, `_cuda`, `_opencl`
with the array names. So an array created on the CUDA backend would be named
`myarray_cuda`.
suggested technique would be to use a suffix of `_cpu`, `_cuda`, `_metal`, or
`_opencl` with the array names. So an array created on the CUDA backend would
be named `myarray_cuda`.

If you have not used the af::setBackend function anywhere in your code, then
you do not have to worry about this as all the arrays will be created on the
same default backend.

### Don't: Do not use custom kernels (CUDA/OpenCL) with the Unified backend
### Don't: Do not use custom kernels (CUDA/Metal/OpenCL) with the Unified backend

This is another area that is a no go when using the Unified backend. It not
recommended that you use custom kernels with unified backend. This is mainly
Expand Down
5 changes: 3 additions & 2 deletions docs/pages/using_on_osx.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ how to use CMake. To link with a specific backend directly, replace the

* `ArrayFire::afcpu` for CPU backend.
* `ArrayFire::afcuda` for CUDA backend.
* `ArrayFire::afmetal` for Metal backend.
* `ArrayFire::afopencl` for OpenCL backend.

Next we need to instruct CMake to create build instructions and then compile. We
Expand Down Expand Up @@ -92,8 +93,8 @@ followed our installation instructions.
Similarly, you will need to specify the path to the ArrayFire library using the
`-L` option (e.g. `-L/opt/arrayfire/lib`) followed by the specific ArrayFire
library you wish to use using the `-l` option (for example `-lafcpu`,
`-lafopencl`, `-lafcuda`, or `-laf` for the CPU, OpenCL, CUDA, and unified
backends respectively.
`-lafopencl`, `-lafcuda`, `-lafmetal`, or `-laf` for the CPU, OpenCL, CUDA,
Metal, and unified backends respectively.

Here is a minimal example Makefile which uses ArrayFire's CPU backend:

Expand Down
3 changes: 2 additions & 1 deletion include/af/defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,8 @@ typedef enum {
AF_BACKEND_CPU = 1, ///< CPU a.k.a sequential algorithms
AF_BACKEND_CUDA = 2, ///< CUDA Compute Backend
AF_BACKEND_OPENCL = 4, ///< OpenCL Compute Backend
AF_BACKEND_ONEAPI = 8 ///< OneAPI Compute Backend
AF_BACKEND_ONEAPI = 8, ///< OneAPI Compute Backend
AF_BACKEND_METAL = 16 ///< Metal Compute Backend
} af_backend;
#endif

Expand Down
4 changes: 2 additions & 2 deletions src/api/c/confidence_connected.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ af_array ccHelper(const Array<T>& img, const Array<uint>& seedx,

Array<CT> segmented = floodFill(in, seedx, seedy, CT(1), lower, upper);

if (std::abs<CT>(s1var) < epsilon) {
if (std::abs(s1var) < epsilon) {
// If variance is close to zero, stop after initial segmentation
return getHandle(labelSegmented(segmented));
}
Expand Down Expand Up @@ -188,7 +188,7 @@ af_array ccHelper(const Array<T>& img, const Array<uint>& seedx,
if (newLow > minSeedIntensity) { newLow = minSeedIntensity; }
if (newHigh < maxSeedIntensity) { newHigh = maxSeedIntensity; }

if (std::abs<CT>(validsVar) < epsilon) {
if (std::abs(validsVar) < epsilon) {
// If variance is close to zero, discontinue iterating.
continueLoop = false;
}
Expand Down
10 changes: 10 additions & 0 deletions src/api/cpp/array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,16 @@ array::~array() {
release_func(get());
break;
}
case AF_BACKEND_METAL: {
static auto *metal_handle =
arrayfire::unified::getActiveHandle();
static auto release_func =
reinterpret_cast<af_release_array_ptr>(
getFunctionPointer(metal_handle,
"af_release_array"));
release_func(get());
break;
}
case AF_BACKEND_DEFAULT:
assert(1 != 1 &&
"AF_BACKEND_DEFAULT cannot be set as a backend for "
Expand Down
Loading