Getting Started
Introduction
ArrayFire is a high performance software library for parallel computing with an easy-to-use API. ArrayFire abstracts away much of the details of programming parallel architectures by providing a high-level container object, the array, that represents data stored on a CPU, GPU, FPGA, or other type of accelerator. This abstraction permits developers to write massively parallel applications in a high-level language where they need not be concerned about low-level optimizations that are frequently required to achieve high throughput on most parallel architectures.
Supported data types
ArrayFire provides one generic container object, the array on which functions and mathematical operations are performed. The array
can represent one of many different basic data types:
f32 real single-precision (
float
)c32 complex single-precision (
cfloat
)f64 real double-precision (
double
)c64 complex double-precision (
cdouble
)f16 real half-precision (
half_float::half
)b8 8-bit boolean values (
bool
)s32 32-bit signed integer (
int
)u32 32-bit unsigned integer (
unsigned
)u8 8-bit unsigned values (
unsigned char
)s64 64-bit signed integer (
intl
)u64 64-bit unsigned integer (
uintl
)s16 16-bit signed integer (
short
)u16 16-bit unsigned integer (
unsigned short
)
Most of these data types are supported on all modern GPUs; however, some older devices may lack support for double precision arrays. In this case, a runtime error will be generated when the array is constructed.
If not specified otherwise, array`s are created as single precision floating point numbers (:literal:`f32
).
Creating and populating an ArrayFire array
ArrayFire arrays represent memory stored on the device. As such, creation and population of an array will consume memory on the device which cannot freed until the array
object goes out of scope. As device memory allocation can be expensive, ArrayFire also includes a memory manager which will re-use device memory whenever possible.
Arrays can be created using one of the array constructors. Below we show how to create 1D, 2D, and 3D arrays with uninitialized values:
# Arrays may be created using the array constructor and dimensioned
# as 1D, 2D, 3D; however, the values in these arrays will be undefined
import arrayfire as af
array = af.constant(0, (100,))
array_2d = af.constant(0, (10, 100))
array_3d = af.constant(0, (10, 10, 10))
However, uninitialized memory is likely not useful in your application. ArrayFire provides several convenient functions for creating arrays that contain pre-populated values including constants, uniform random numbers, uniform normally distributed numbers, and the identity matrix:
import arrayfire as af
# Generate an array of size three filled with zeros.
# If no data type is specified, ArrayFire defaults to f32.
# The constant function generates the data on the device.
zeroes = af.constant(0, (3,))
# Generate a 1x4 array of uniformly distributed [0,1] random numbers
# The randu function generates the data on the device.
rand1 = af.randu((1, 4))
# Generate a 2x2 array (or matrix, if you prefer) of random numbers
# sampled from a normal distribution.
# The randn function generates data on the device.
rand2 = af.randu((2, 2))
# Generate a 3x3 identity matrix. The data is generated on the device.
iden = af.identity((3, 3))
# Lastly, create a 2x1 array (column vector) of uniformly distributed
# 32-bit complex numbers (c32 data type):
randcplx = af.randu((2, 1))
A complete list of ArrayFire functions that automatically generate data on the device may be found on the functions to create arrays page. As stated above, the default data type for arrays is f32 (a 32-bit floating point number) unless specified otherwise.
ArrayFire arrays may also be populated from data found on the host. For example:
import arrayfire as af
# Create a six-element array on the host
hA = [0, 1, 2, 3, 4, 5]
# Which can be copied into an ArrayFire Array using the pointer copy
# constructor. Here we copy the data into a 2x3 matrix:
A = af.moddims(af.Array(hA), (2, 3))
# ArrayFire provides a convenince function for printing array
# objects in case you wish to see how the data is stored:
print(A)
# todo how to create complex numbers
ArrayFire also supports array initialization from memory already on the GPU. For example, with CUDA one can populate an array
directly using a call to cudaMemcpy
:
import numpy as np
import pycuda.driver as cuda
import arrayfire as af
# Create an array on the host
host_ptr = af.Array([0, 1, 2, 3, 4, 5])
# Create an ArrayFire array 'a' from host_ptr (2x3 matrix)
A = af.moddims(host_ptr, (2, 3))
# Allocate CUDA device memory and copy data from host to device
device_ptr = cuda.mem_alloc(host_ptr.nbytes)
cuda.memcpy_htod(device_ptr, host_ptr)
# Create an ArrayFire array 'b' from CUDA-allocated device memory (2x3 matrix)
b = af.Array(device_ptr, dims=(2, 3), is_device=True)
# Note: ArrayFire takes ownership of `device_ptr`, so no need to free it manually
# Clean up CUDA resources (not necessary due to Python's automatic memory management)
# cuda.mem_free(device_ptr)
Similar functionality exists for OpenCL too. If you wish to intermingle ArrayFire with CUDA or OpenCL code, we suggest you consult the CUDA interoperability or OpenCL interoperability pages for detailed instructions.
ArrayFire array contents, dimensions, and properties
ArrayFire provides several functions to determine various aspects of arrays. This includes functions to print the contents, query the dimensions, and determine various other aspects of arrays.
The print function can be used to print arrays that have already been generated or any expression involving arrays:
import arrayfire as af
# Generate two arrays
a = af.randu((2, 2)) # Create a 2x2 array with random numbers between [0, 1]
b = af.constant(1, (2, 1)) # Create a 2x1 array filled with constant value 1
# Print arrays 'a' and 'b' to the console
print("Array 'a':", a)
print("Array 'b':", b)
# Print the results of an expression involving arrays
result = a.col(0) + b + 0.4 # Perform operation: first column of 'a' + 'b' + 0.4
print("Result of expression (a.col(0) + b + 0.4):")
print(result)
The dimensions of an array may be determined using either a dim4 object or by accessing the dimensions directly using the dims() and numdims() functions:
import arrayfire as af
# Create a 4x5x2 array of uniformly distributed random numbers
a = af.randu((4, 5, 2))
# Determine the number of dimensions using the `numdims()` function
print("numdims(a):", a.numdims()) # Print the number of dimensions (should be 3)
# Print the size of the individual dimensions using the `dims()` function
print("dims =", a.dims()) # Print dimensions as a tuple (4, 5, 2)
# Alternatively, access dimensions using a dim4 object
dims = a.dims()
print("dims =", dims[0], dims[1]) # Print dimensions separately (4, 5)
In addition to dimensions, arrays also carry several properties including methods to determine the underlying type and size (in bytes). You can even determine whether the array is empty, real/complex, a row/column, or a scalar or a vector:
import arrayfire as af
# Create an example ArrayFire array 'a'
a = af.randu((4, 5)) # Example array of dtype float32
# Get the type stored in the array
print("underlying type:", a.type())
# Check if the array contains complex or real values
print("is complex?", a.iscomplex(), " is real?", a.isreal())
# Check if the array is a vector, column vector, or row vector
print("is vector?", a.isvector(), " column?", a.iscolumn(), " row?", a.isrow())
# Check if the array is empty, and determine its total elements and memory usage
print("empty?", a.isempty(), " total elements:", a.elements(), " bytes:", a.bytes())
For further information on these capabilities, we suggest you consult the full documentation on the array.
Writing mathematical expressions in ArrayFire
ArrayFire leverages an advanced Just-In-Time (JIT) compilation engine that optimizes array operations by minimizing the number of CUDA/OpenCL kernels used. In Python, ArrayFire functions operate similarly to a vector library. This means that typical element-wise operations, such as c[i] = a[i] + b[i]
in C, can be expressed more succinctly as c = a + b
, eliminating the need for explicit indexing.
When multiple array operations are involved, ArrayFire’s JIT engine consolidates them through “kernel fusion”. This technique not only reduces the frequency of kernel invocations but also optimizes memory usage by eliminating redundant global memory operations. The JIT functionality extends seamlessly across Python function boundaries, continuing until a non-JIT function is encountered or a synchronization operation is explicitly invoked in the code.
ArrayFire provides a broad spectrum of functions tailored for element-wise operations. It supports standard arithmetic operators (+, -, *, /) as well as a variety of transcendental functions (sin, cos, log, sqrt, etc.). These capabilities empower users to perform complex computations efficiently and effectively.
import arrayfire as af
# Generate a 3x3 array of uniformly distributed random numbers
R = af.randu((3, 3))
print(af.constant(1, (3, 3)) + af.join(af.sin(R))) # will be c32
# Rescale complex values to unit circle
a = af.randn(5)
print(a / af.abs(a))
# Calculate L2 norm of vectors
X = af.randn((3, 4))
print(af.sqrt(af.sum(af.pow(X, 2)))) # norm of every column vector
print(af.sqrt(af.sum(af.pow(X, 2), 0))) # same as above
print(af.sqrt(af.sum(af.pow(X, 2), 1))) # norm of every row vector
To see the complete list of functions please consult the documentation on mathematical, linear algebra, signal processing, and statistics.
Mathematical constants
In Python, ArrayFire provides several platform-independent constants such as Pi, NaN, and Inf. If ArrayFire lacks a specific constant you require, you can create it using the af.constant array constructor.
These constants are universally applicable across all ArrayFire functions. Below, we illustrate their usage in element selection and a mathematical expression:
import math
import arrayfire as af
# Generate a 5x5 array of uniformly distributed random numbers
A = af.randu((5, 5))
# Set elements in A greater than 0.5 to NaN
A[af.where(A > 0.5)] = af.NaN
# Generate arrays x and y with 10 million random numbers each
x = af.randu(int(10e6))
y = af.randu(int(10e6))
# Estimate Pi using Monte Carlo method
pi_est = 4 * af.sum(af.hypot(x, y) < 1) / 10e6
# Print the estimation error compared to math.pi
print("estimation error:", abs(math.pi - pi_est))
Please note that our constants may, at times, conflict with macro definitions in standard header files. When this occurs, please refer to our constants using the af::
namespace.
Indexing
Like all functions in ArrayFire, indexing is also executed in parallel on the OpenCL/CUDA devices. Because of this, indexing becomes part of a JIT operation and is accomplished using parentheses instead of square brackets (i.e. as A(0)
instead of A[0]
). To index af::
arrays you may use one or a combination of the following functions:
integer scalars
:
representing the entire descriptionbegin:end:step
representing a linear sequence/slicerow(i)
orcol(i)
specifying a single row/columnrows(first,last)
orcols(first,last)
specifying a span of rows or columns
Please see the indexing page for several examples of how to use these functions.
Bitwise operators
In addition to supporting standard mathematical functions, arrays that contain integer data types also support bitwise operators including and, or, and shift:
import arrayfire as af
# Define host arrays
h_A = [1, 1, 0, 0, 4, 0, 0, 2, 0]
h_B = [1, 0, 1, 0, 1, 0, 1, 1, 1]
# Create ArrayFire arrays A and B from host arrays
A = af.Array(h_A, dims=(3, 3))
B = af.Array(h_B, dims=(3, 3))
# Print arrays A and B
print(A)
print(B)
# Perform bitwise operations
A_and_B = A & B
A_or_B = A | B
A_xor_B = A ^ B
# Print results of bitwise operations
print(A_and_B)
print(A_or_B)
print(A_xor_B)
Sample using Python API
import arrayfire as af
def main():
# Generate random values
a = af.randu(10000, dtype=af.Dtype.f32)
# Sum all the values
result = af.sum(a)
print(f"sum: {result}\n")