table of Contents
Preface
In "Playing with ArrayFire: 07 Array and Matrix Operations", we have learned about ArrayFire's array and matrix operations. In this article, we will continue to understand the backend of ArrayFire.
1. Introduction
ArrayFire's unified backend was introduced in version 3.2. Although this is not a standalone backend, it allows users to switch between different ArrayFire backends (CPU, CUDA and OpenCL) at runtime.
Two, unified compilation
The steps for compiling with a unified backend are the same as those for compiling with any other backend. The only difference is the need to link to the executable file af library (such as Linux, libaf.so , OSX's libaf.dylib , Windows of af.lib , etc.). If you want to use it with CMake, use the ArrayFire_Unified_LIBRARIES variable.
Three, use a unified backend
The unified backend will dynamically load the backend library. The backend priority is CUDA -> OpenCL -> CPU . The most important thing to note here is that all libraries that the ArrayFire library depends on need to be in the environment path.
- LD_LIBRARY_PATH -> Linux, Unix, OSX
- DYLD_LIBRARY_PATH -> OSX
- PATH -> Windows
If any libraries are missing, then the ArrayFire library will fail to load and the backend will be marked as unavailable. If the path is not in the system path, the ArrayFire library may appear in the AF_PATH or AF_BUILD_PATH environment variable. These paths are considered alternate paths in case these files are not found in the system path. However, all other upstream libraries of the ArrayFire library must appear in the system path variables shown above.
Four, switch backend
af_backend enumerates possible backends for storage. To select the backend, call the af::setBackend function, as shown below:
af::setBackend(AF_BACKEND_CUDA); // Sets CUDA as current backend
You can call the af::getBackendCount function to get the number of backends available ( libaf successfully loaded * the number of backend libraries).
Five, case
#include <arrayfire.h>
void testBackend()
{
af::info();
af_print(af::randu(5, 4));
}
int main()
{
try {
printf("Trying CPU Backend\n");
af::setBackend(AF_BACKEND_CPU);
testBackend();
} catch (af::exception& e) {
printf("Caught exception when trying CPU backend\n");
fprintf(stderr, "%s\n", e.what());
}
try {
printf("Trying CUDA Backend\n");
af::setBackend(AF_BACKEND_CUDA);
testBackend();
} catch (af::exception& e) {
printf("Caught exception when trying CUDA backend\n");
fprintf(stderr, "%s\n", e.what());
}
try {
printf("Trying OpenCL Backend\n");
af::setBackend(AF_BACKEND_OPENCL);
testBackend();
} catch (af::exception& e) {
printf("Caught exception when trying OpenCL backend\n");
fprintf(stderr, "%s\n", e.what());
}
return 0;
}
The output result is as follows:
Trying CPU Backend
ArrayFire v3.2.0 (CPU, 64-bit Linux, build fc7630f)
[0] Intel: Intel(R) Core(TM) i7-4770K CPU @ 3.50GHz Max threads(8)
af::randu(5, 4)
[5 4 1 1]
0.0000 0.2190 0.3835 0.5297
0.1315 0.0470 0.5194 0.6711
0.7556 0.6789 0.8310 0.0077
0.4587 0.6793 0.0346 0.3834
0.5328 0.9347 0.0535 0.0668
Trying CUDA Backend
ArrayFire v3.2.0 (CUDA, 64-bit Linux, build fc7630f)
Platform: CUDA Toolkit 7.5, Driver: 355.11
[0] Quadro K5000, 4093 MB, CUDA Compute 3.0
af::randu(5, 4)
[5 4 1 1]
0.7402 0.4464 0.7762 0.2920
0.9210 0.6673 0.2948 0.3194
0.0390 0.1099 0.7140 0.8109
0.9690 0.4702 0.3585 0.1541
0.9251 0.5132 0.6814 0.4452
Trying OpenCL Backend
ArrayFire v3.2.0 (OpenCL, 64-bit Linux, build fc7630f)
[0] NVIDIA : Quadro K5000
-1- INTEL : Intel(R) Core(TM) i7-4770K CPU @ 3.50GHz
af::randu(5, 4)
[5 4 1 1]
0.4107 0.0081 0.6600 0.1046
0.8224 0.3775 0.0764 0.8827
0.9518 0.3027 0.0901 0.1647
0.1794 0.6456 0.5933 0.8060
0.4198 0.5591 0.1098 0.5938
Six, matters needing attention
If you accidentally switch the backend, it is easy to encounter exceptions.
- Don't use array between different backends
ArrayFire checks whether the array input to the function matches the active backend. If an array is created in one backend, but used in another backend, an exception code of 503 ( AF_ERR_ARR_BKND_MISMATCH ) will be thrown .
#include <arrayfire.h>
int main()
{
try {
af::setBackend(AF_BACKEND_CUDA);
af::array A = af::randu(5, 5);
af::setBackend(AF_BACKEND_OPENCL);
af::array B = af::constant(10, 5, 5);
af::array C = af::matmul(A, B); // This will throw an exception
} catch (af::exception& e) {
fprintf(stderr, "%s\n", e.what());
}
return 0;
}
- Use a naming scheme to track array and backend
We recommend that you use a technique to track the array on the backend. One suggestion is to use _cpu , _cuda , _opencl suffixes in the array name . Therefore, the array created on the CUDA backend will be named myarray_cuda. If you haven't used the af::setBackend function in your code , then you don't need to worry about this problem, because all arrays are created on the same default backend.
- Cannot use custom kernel (CUDA/OpenCL) and unified backend at the same time
It is not recommended to use a custom kernel with a unified backend. This is mainly because a unified backend means super portable, and only ArrayFire and native CPU code should be used.