/src/aom/av1/encoder/x86/ml_avx2.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2023, Alliance for Open Media. All rights reserved. |
3 | | * |
4 | | * This source code is subject to the terms of the BSD 2 Clause License and |
5 | | * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License |
6 | | * was not distributed with this source code in the LICENSE file, you can |
7 | | * obtain it at www.aomedia.org/license/software. If the Alliance for Open |
8 | | * Media Patent License 1.0 was not distributed with this source code in the |
9 | | * PATENTS file, you can obtain it at www.aomedia.org/license/patent. |
10 | | */ |
11 | | |
12 | | #include <stdbool.h> |
13 | | #include <assert.h> |
14 | | #include <immintrin.h> |
15 | | |
16 | | #include "config/av1_rtcd.h" |
17 | | #include "av1/encoder/ml.h" |
18 | | #include "av1/encoder/x86/ml_sse3.h" |
19 | | |
20 | | #define CALC_OUTPUT_FOR_2ROWS \ |
21 | 0 | const int index = weight_idx + (2 * i * tot_num_inputs); \ |
22 | 0 | const __m256 weight0 = _mm256_loadu_ps(&weights[index]); \ |
23 | 0 | const __m256 weight1 = _mm256_loadu_ps(&weights[index + tot_num_inputs]); \ |
24 | 0 | const __m256 mul0 = _mm256_mul_ps(inputs256, weight0); \ |
25 | 0 | const __m256 mul1 = _mm256_mul_ps(inputs256, weight1); \ |
26 | 0 | hadd[i] = _mm256_hadd_ps(mul0, mul1); |
27 | | |
28 | | static inline void nn_propagate_8to1( |
29 | | const float *const inputs, const float *const weights, |
30 | | const float *const bias, int num_inputs_to_process, int tot_num_inputs, |
31 | 0 | int num_outputs, float *const output_nodes, int is_clip_required) { |
32 | | // Process one output row at a time. |
33 | 0 | for (int out = 0; out < num_outputs; out++) { |
34 | 0 | __m256 in_result = _mm256_setzero_ps(); |
35 | 0 | float bias_val = bias[out]; |
36 | 0 | for (int in = 0; in < num_inputs_to_process; in += 8) { |
37 | 0 | const __m256 inputs256 = _mm256_loadu_ps(&inputs[in]); |
38 | 0 | const int weight_idx = in + (out * tot_num_inputs); |
39 | 0 | const __m256 weight0 = _mm256_loadu_ps(&weights[weight_idx]); |
40 | 0 | const __m256 mul0 = _mm256_mul_ps(inputs256, weight0); |
41 | 0 | in_result = _mm256_add_ps(in_result, mul0); |
42 | 0 | } |
43 | 0 | const __m128 low_128 = _mm256_castps256_ps128(in_result); |
44 | 0 | const __m128 high_128 = _mm256_extractf128_ps(in_result, 1); |
45 | 0 | const __m128 sum_par_0 = _mm_add_ps(low_128, high_128); |
46 | 0 | const __m128 sum_par_1 = _mm_hadd_ps(sum_par_0, sum_par_0); |
47 | 0 | const __m128 sum_tot = |
48 | 0 | _mm_add_ps(_mm_shuffle_ps(sum_par_1, sum_par_1, 0x99), sum_par_1); |
49 | |
|
50 | 0 | bias_val += _mm_cvtss_f32(sum_tot); |
51 | 0 | if (is_clip_required) bias_val = AOMMAX(bias_val, 0); |
52 | 0 | output_nodes[out] = bias_val; |
53 | 0 | } |
54 | 0 | } |
55 | | |
56 | | static inline void nn_propagate_8to4( |
57 | | const float *const inputs, const float *const weights, |
58 | | const float *const bias, int num_inputs_to_process, int tot_num_inputs, |
59 | 0 | int num_outputs, float *const output_nodes, int is_clip_required) { |
60 | 0 | __m256 hadd[2]; |
61 | 0 | for (int out = 0; out < num_outputs; out += 4) { |
62 | 0 | __m128 bias_reg = _mm_loadu_ps(&bias[out]); |
63 | 0 | __m128 in_result = _mm_setzero_ps(); |
64 | 0 | for (int in = 0; in < num_inputs_to_process; in += 8) { |
65 | 0 | const __m256 inputs256 = _mm256_loadu_ps(&inputs[in]); |
66 | 0 | const int weight_idx = in + (out * tot_num_inputs); |
67 | | // Process two output row at a time. |
68 | 0 | for (int i = 0; i < 2; i++) { |
69 | 0 | CALC_OUTPUT_FOR_2ROWS |
70 | 0 | } |
71 | |
|
72 | 0 | const __m256 sum_par = _mm256_hadd_ps(hadd[0], hadd[1]); |
73 | 0 | const __m128 low_128 = _mm256_castps256_ps128(sum_par); |
74 | 0 | const __m128 high_128 = _mm256_extractf128_ps(sum_par, 1); |
75 | 0 | const __m128 result = _mm_add_ps(low_128, high_128); |
76 | |
|
77 | 0 | in_result = _mm_add_ps(in_result, result); |
78 | 0 | } |
79 | |
|
80 | 0 | in_result = _mm_add_ps(in_result, bias_reg); |
81 | 0 | if (is_clip_required) in_result = _mm_max_ps(in_result, _mm_setzero_ps()); |
82 | 0 | _mm_storeu_ps(&output_nodes[out], in_result); |
83 | 0 | } |
84 | 0 | } |
85 | | |
86 | | static inline void nn_propagate_8to8( |
87 | | const float *const inputs, const float *const weights, |
88 | | const float *const bias, int num_inputs_to_process, int tot_num_inputs, |
89 | 0 | int num_outputs, float *const output_nodes, int is_clip_required) { |
90 | 0 | __m256 hadd[4]; |
91 | 0 | for (int out = 0; out < num_outputs; out += 8) { |
92 | 0 | __m256 bias_reg = _mm256_loadu_ps(&bias[out]); |
93 | 0 | __m256 in_result = _mm256_setzero_ps(); |
94 | 0 | for (int in = 0; in < num_inputs_to_process; in += 8) { |
95 | 0 | const __m256 inputs256 = _mm256_loadu_ps(&inputs[in]); |
96 | 0 | const int weight_idx = in + (out * tot_num_inputs); |
97 | | // Process two output rows at a time. |
98 | 0 | for (int i = 0; i < 4; i++) { |
99 | 0 | CALC_OUTPUT_FOR_2ROWS |
100 | 0 | } |
101 | 0 | const __m256 hh0 = _mm256_hadd_ps(hadd[0], hadd[1]); |
102 | 0 | const __m256 hh1 = _mm256_hadd_ps(hadd[2], hadd[3]); |
103 | |
|
104 | 0 | __m256 ht_0 = _mm256_permute2f128_ps(hh0, hh1, 0x20); |
105 | 0 | __m256 ht_1 = _mm256_permute2f128_ps(hh0, hh1, 0x31); |
106 | |
|
107 | 0 | __m256 result = _mm256_add_ps(ht_0, ht_1); |
108 | 0 | in_result = _mm256_add_ps(in_result, result); |
109 | 0 | } |
110 | 0 | in_result = _mm256_add_ps(in_result, bias_reg); |
111 | 0 | if (is_clip_required) |
112 | 0 | in_result = _mm256_max_ps(in_result, _mm256_setzero_ps()); |
113 | 0 | _mm256_storeu_ps(&output_nodes[out], in_result); |
114 | 0 | } |
115 | 0 | } |
116 | | |
117 | | static inline void nn_propagate_input_multiple_of_8( |
118 | | const float *const inputs, const float *const weights, |
119 | | const float *const bias, int num_inputs_to_process, int tot_num_inputs, |
120 | 0 | bool is_output_layer, int num_outputs, float *const output_nodes) { |
121 | | // The saturation of output is considered for hidden layer which is not equal |
122 | | // to final hidden layer. |
123 | 0 | const int is_clip_required = |
124 | 0 | !is_output_layer && num_inputs_to_process == tot_num_inputs; |
125 | 0 | if (num_outputs % 8 == 0) { |
126 | 0 | nn_propagate_8to8(inputs, weights, bias, num_inputs_to_process, |
127 | 0 | tot_num_inputs, num_outputs, output_nodes, |
128 | 0 | is_clip_required); |
129 | 0 | } else if (num_outputs % 4 == 0) { |
130 | 0 | nn_propagate_8to4(inputs, weights, bias, num_inputs_to_process, |
131 | 0 | tot_num_inputs, num_outputs, output_nodes, |
132 | 0 | is_clip_required); |
133 | 0 | } else { |
134 | 0 | nn_propagate_8to1(inputs, weights, bias, num_inputs_to_process, |
135 | 0 | tot_num_inputs, num_outputs, output_nodes, |
136 | 0 | is_clip_required); |
137 | 0 | } |
138 | 0 | } |
139 | | |
140 | | void av1_nn_predict_avx2(const float *input_nodes, |
141 | | const NN_CONFIG *const nn_config, int reduce_prec, |
142 | 0 | float *const output) { |
143 | 0 | float buf[2][NN_MAX_NODES_PER_LAYER]; |
144 | 0 | int buf_index = 0; |
145 | 0 | int num_inputs = nn_config->num_inputs; |
146 | 0 | assert(num_inputs > 0 && num_inputs <= NN_MAX_NODES_PER_LAYER); |
147 | | |
148 | 0 | for (int layer = 0; layer <= nn_config->num_hidden_layers; layer++) { |
149 | 0 | const float *layer_weights = nn_config->weights[layer]; |
150 | 0 | const float *layer_bias = nn_config->bias[layer]; |
151 | 0 | bool is_output_layer = layer == nn_config->num_hidden_layers; |
152 | 0 | float *const output_nodes = is_output_layer ? output : &buf[buf_index][0]; |
153 | 0 | const int num_outputs = is_output_layer |
154 | 0 | ? nn_config->num_outputs |
155 | 0 | : nn_config->num_hidden_nodes[layer]; |
156 | 0 | assert(num_outputs > 0 && num_outputs <= NN_MAX_NODES_PER_LAYER); |
157 | | |
158 | | // Process input multiple of 8 using AVX2 intrinsic. |
159 | 0 | if (num_inputs % 8 == 0) { |
160 | 0 | nn_propagate_input_multiple_of_8(input_nodes, layer_weights, layer_bias, |
161 | 0 | num_inputs, num_inputs, is_output_layer, |
162 | 0 | num_outputs, output_nodes); |
163 | 0 | } else { |
164 | | // When number of inputs is not multiple of 8, use hybrid approach of AVX2 |
165 | | // and SSE3 based on the need. |
166 | 0 | const int in_mul_8 = num_inputs / 8; |
167 | 0 | const int num_inputs_to_process = in_mul_8 * 8; |
168 | 0 | int bias_is_considered = 0; |
169 | 0 | if (in_mul_8) { |
170 | 0 | nn_propagate_input_multiple_of_8( |
171 | 0 | input_nodes, layer_weights, layer_bias, num_inputs_to_process, |
172 | 0 | num_inputs, is_output_layer, num_outputs, output_nodes); |
173 | 0 | bias_is_considered = 1; |
174 | 0 | } |
175 | |
|
176 | 0 | const float *out_temp = bias_is_considered ? output_nodes : layer_bias; |
177 | 0 | const int input_remaining = num_inputs % 8; |
178 | 0 | if (input_remaining % 4 == 0 && num_outputs % 8 == 0) { |
179 | 0 | for (int out = 0; out < num_outputs; out += 8) { |
180 | 0 | __m128 out_h = _mm_loadu_ps(&out_temp[out + 4]); |
181 | 0 | __m128 out_l = _mm_loadu_ps(&out_temp[out]); |
182 | 0 | for (int in = in_mul_8 * 8; in < num_inputs; in += 4) { |
183 | 0 | av1_nn_propagate_4to8_sse3(&input_nodes[in], |
184 | 0 | &layer_weights[out * num_inputs + in], |
185 | 0 | &out_h, &out_l, num_inputs); |
186 | 0 | } |
187 | 0 | if (!is_output_layer) { |
188 | 0 | const __m128 zero = _mm_setzero_ps(); |
189 | 0 | out_h = _mm_max_ps(out_h, zero); |
190 | 0 | out_l = _mm_max_ps(out_l, zero); |
191 | 0 | } |
192 | 0 | _mm_storeu_ps(&output_nodes[out + 4], out_h); |
193 | 0 | _mm_storeu_ps(&output_nodes[out], out_l); |
194 | 0 | } |
195 | 0 | } else if (input_remaining % 4 == 0 && num_outputs % 4 == 0) { |
196 | 0 | for (int out = 0; out < num_outputs; out += 4) { |
197 | 0 | __m128 outputs = _mm_loadu_ps(&out_temp[out]); |
198 | 0 | for (int in = in_mul_8 * 8; in < num_inputs; in += 4) { |
199 | 0 | av1_nn_propagate_4to4_sse3(&input_nodes[in], |
200 | 0 | &layer_weights[out * num_inputs + in], |
201 | 0 | &outputs, num_inputs); |
202 | 0 | } |
203 | 0 | if (!is_output_layer) outputs = _mm_max_ps(outputs, _mm_setzero_ps()); |
204 | 0 | _mm_storeu_ps(&output_nodes[out], outputs); |
205 | 0 | } |
206 | 0 | } else if (input_remaining % 4 == 0) { |
207 | 0 | for (int out = 0; out < num_outputs; out++) { |
208 | 0 | __m128 outputs = _mm_load1_ps(&out_temp[out]); |
209 | 0 | for (int in = in_mul_8 * 8; in < num_inputs; in += 4) { |
210 | 0 | av1_nn_propagate_4to1_sse3(&input_nodes[in], |
211 | 0 | &layer_weights[out * num_inputs + in], |
212 | 0 | &outputs); |
213 | 0 | } |
214 | 0 | if (!is_output_layer) outputs = _mm_max_ps(outputs, _mm_setzero_ps()); |
215 | 0 | output_nodes[out] = _mm_cvtss_f32(outputs); |
216 | 0 | } |
217 | 0 | } else { |
218 | | // Use SSE instructions for scalar operations to avoid the latency |
219 | | // of swapping between SIMD and FPU modes. |
220 | 0 | for (int out = 0; out < num_outputs; out++) { |
221 | 0 | __m128 outputs = _mm_load1_ps(&out_temp[out]); |
222 | 0 | for (int in_node = in_mul_8 * 8; in_node < num_inputs; in_node++) { |
223 | 0 | __m128 input = _mm_load1_ps(&input_nodes[in_node]); |
224 | 0 | __m128 weight = |
225 | 0 | _mm_load1_ps(&layer_weights[num_inputs * out + in_node]); |
226 | 0 | outputs = _mm_add_ps(outputs, _mm_mul_ps(input, weight)); |
227 | 0 | } |
228 | 0 | if (!is_output_layer) outputs = _mm_max_ps(outputs, _mm_setzero_ps()); |
229 | 0 | output_nodes[out] = _mm_cvtss_f32(outputs); |
230 | 0 | } |
231 | 0 | } |
232 | 0 | } |
233 | | // Before processing the next layer, treat the output of current layer as |
234 | | // input to next layer. |
235 | 0 | input_nodes = output_nodes; |
236 | 0 | num_inputs = num_outputs; |
237 | 0 | buf_index = 1 - buf_index; |
238 | 0 | } |
239 | 0 | if (reduce_prec) av1_nn_output_prec_reduce(output, nn_config->num_outputs); |
240 | 0 | } |