aboutsummaryrefslogtreecommitdiff
path: root/docs/contributor_guide/implementation_topics.dox
blob: 6ca78f98e7795a1238225f57a17832d3acc540cf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
///
/// Copyright (c) 2017-2021, 2024 Arm Limited.
///
/// SPDX-License-Identifier: MIT
///
/// Permission is hereby granted, free of charge, to any person obtaining a copy
/// of this software and associated documentation files (the "Software"), to
/// deal in the Software without restriction, including without limitation the
/// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
/// sell copies of the Software, and to permit persons to whom the Software is
/// furnished to do so, subject to the following conditions:
///
/// The above copyright notice and this permission notice shall be included in all
/// copies or substantial portions of the Software.
///
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
/// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
/// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
/// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
/// SOFTWARE.
///
namespace arm_compute
{
/** @page implementation_topic Implementation Topics

@section implementation_topic_assembly_kernels Assembly kernels

Arm Compute Library contains a collection of highly optimized assembly kernels for Arm® A profile architecture. At runtime the
library selects the best kernel based on the CPU detected. For example if the CPU supports the dot product instruction
the library will choose a GEMM kernel which uses the dot product instruction. There are various kernels using Neon™ and
architecture extensions like FP16, Dot product, SVE, SVE2 and SME.

For example, some assembly kernels are located in the folders:
- src/core/NEON/kernels/arm_gemm/kernels
- src/core/NEON/kernels/arm_gemm/pooling
- src/core/NEON/kernels/arm_conv/depthwise


The assembly kernels are written using assembly mnemonics and the .inst directive which inserts the machine code to the output directly.

Below you can see a code block from one of the kernels in the library which uses the .inst directive to generate the sdot instruction.
This code can be found in the kernel @ref src/core/NEON/kernels/arm_gemm/kernels/a64_hybrid_s8qa_dot_4x16/a55.cpp

@code{.cpp}
".inst 0x4f80eb10  // sdot v16.4s, v24.16b, v0.4b[2]\n"
".inst 0x4f81eb14  // sdot v20.4s, v24.16b, v1.4b[2]\n"
" ldr d24, [x12, #0xf0]\n"
" ldr x20, [x12, #0xf8]\n"
" .inst 0x4f80ebd1  // sdot v17.4s, v30.16b, v0.4b[2]\n"
" .inst 0x4f81ebd5  // sdot v21.4s, v30.16b, v1.4b[2]\n"
" mov v27.d[1], x23\n"
" .inst 0x4f80ebb2  // sdot v18.4s, v29.16b, v0.4b[2]\n"
" mov v26.d[1], x22\n"
" .inst 0x4f81ebb6  // sdot v22.4s, v29.16b, v1.4b[2]\n"
" mov v25.d[1], x21\n"
" .inst 0x4f80eb93  // sdot v19.4s, v28.16b, v0.4b[2]\n"
" mov v24.d[1], x20\n"
" .inst 0x4f81eb97  // sdot v23.4s, v28.16b, v1.4b[2]\n"
" add x9, x9, #0x10\n"
" add x28, x28, #0x10\n"
" add x12, x12, #0x100\n"
" .inst 0x4fa0eb70  // sdot v16.4s, v27.16b, v0.4b[3]\n"
" .inst 0x4fa1eb74  // sdot v20.4s, v27.16b, v1.4b[3]\n"
" .inst 0x4fa0eb51  // sdot v17.4s, v26.16b, v0.4b[3]\n"
" .inst 0x4fa1eb55  // sdot v21.4s, v26.16b, v1.4b[3]\n"
@endcode

Note that every occurrence of .inst is accompanied by a comment with the original opcode for readability purposes.

The reason for using the opcodes instead of the mnemonic is that this approach will work on any toolchain, including the ones without support for the dot product mnemonic. The .inst directive is used to generate many other instructions and ensuring the code will compile on older toolchains that do not support them.

@section implementation_topic_windows Windows

A @ref Window represents a workload to execute, it can handle up to @ref Coordinates::num_max_dimensions dimensions.
Each dimension is defined by a start, end and step.

It can split into subwindows as long as *all* the following rules remain true for all the dimensions:

- max[n].start() <= sub[n].start() < max[n].end()
- sub[n].start() < sub[n].end() <= max[n].end()
- max[n].step() == sub[n].step()
- (sub[n].start() - max[n].start()) % max[n].step() == 0
- (sub[n].end() - sub[n].start()) % max[n].step() == 0

@section implementation_topic_kernels Kernels

Each implementation of the @ref IKernel interface (base class of all the kernels in the core library) works in the same way:

OpenCL kernels:

@code{.cpp}
// Initialize the CLScheduler with the default context and default command queue
// Implicitly initializes the CLKernelLibrary to use ./cl_kernels as location for OpenCL kernels files and sets a default device for which OpenCL programs are built.
CLScheduler::get().default_init();

cl::CommandQueue q = CLScheduler::get().queue();
//Create a kernel object:
MyKernel kernel;
// Initialize the kernel with the input/output and options you want to use:
kernel.configure( input, output, option0, option1);
// Retrieve the execution window of the kernel:
const Window& max_window = kernel.window();
// Run the whole kernel in the current thread:
kernel.run( q, max_window ); // Enqueue the kernel to process the full window on the default queue

// Wait for the processing to complete:
q.finish();
@endcode

Neon / CPP kernels:

@code{.cpp}
//Create a kernel object:
MyKernel kernel;
// Initialize the kernel with the input/output and options you want to use:
kernel.configure( input, output, option0, option1);
// Retrieve the execution window of the kernel:
const Window& max_window = kernel.window();
// Run the whole kernel in the current thread:
kernel.run( max_window ); // Run the kernel on the full window
@endcode

@section implementation_topic_multithreading Multi-threading

The previous section shows how to run a Arm® Neon™ / CPP kernel in the current thread, however if your system has several CPU cores, you will probably want the kernel to use several cores. Here is how this can be done:

@code{.cpp}
    ThreadInfo info;
    info.cpu_info = &_cpu_info;

    const Window      &max_window     = kernel->window();
    const unsigned int num_iterations = max_window.num_iterations(split_dimension);
    info.num_threads                  = std::min(num_iterations, _num_threads);

    if(num_iterations == 0)
    {
        return;
    }

    if(!kernel->is_parallelisable() || info.num_threads == 1)
    {
        kernel->run(max_window, info);
    }
    else
    {
        int  t         = 0;
        auto thread_it = _threads.begin();

        for(; t < info.num_threads - 1; ++t, ++thread_it)
        {
            Window win     = max_window.split_window(split_dimension, t, info.num_threads);
            info.thread_id = t;
            thread_it->start(kernel, win, info);
        }

        // Run last part on main thread
        Window win     = max_window.split_window(split_dimension, t, info.num_threads);
        info.thread_id = t;
        kernel->run(win, info);

        try
        {
            for(auto &thread : _threads)
            {
                thread.wait();
            }
        }
        catch(const std::system_error &e)
        {
            std::cerr << "Caught system_error with code " << e.code() << " meaning " << e.what() << '\n';
        }
    }
@endcode

This is a very basic implementation which was originally used in the Arm® Neon™ runtime library by all the Arm® Neon™ functions.

@sa CPPScheduler

@note Some kernels need some local temporary buffer to perform their calculations. In order to avoid memory corruption between threads, the local buffer must be of size: ```memory_needed_per_thread * num_threads``` and a unique thread_id between 0 and num_threads must be assigned to the @ref ThreadInfo object passed to the ```run``` function.


@section implementation_topic_cl_scheduler OpenCL kernel library

All OpenCL kernels used by the library are built and stored in @ref CLKernelLibrary.
If the library is compiled with embed_kernels=0 the application can set the path to the OpenCL kernels by calling @ref CLKernelLibrary::init(), by default the path is set to "./cl_kernels"
*/
} // namespace arm_compute