Полностью подключенный слой (точечный продукт) с использованием AVX - PullRequest
0 голосов
/ 24 сентября 2019

У меня есть следующий код C ++ для выполнения шагов умножения и накопления полностью связанного слоя (без смещения).По сути, я просто делаю точечное произведение, используя вектор (входные данные) и матрицу (весовые коэффициенты).Я использовал векторы AVX для ускорения операции.

const float* src = inputs[0]->buffer();
const float* scl = weights->buffer();
float* dst = outputs[0]->buffer();

SizeVector in_dims = inputs[0]->getTensorDesc().getDims();
SizeVector out_dims = outputs[0]->getTensorDesc().getDims();

const int in_neurons = static_cast<int>(in_dims[1]);
const int out_neurons = static_cast<int>(out_dims[1]);    

for(size_t n = 0; n < out_neurons; n++){
    float accum = 0.0;
    float temp[4] = {0,0,0,0};
    float *p = temp;

    __m128 in, ws, dp;

    for(size_t i = 0; i < in_neurons; i+=4){

        // read and save the weights correctly by applying the mask
        temp[0] = scl[(i+0)*out_neurons + n];
        temp[1] = scl[(i+1)*out_neurons + n];
        temp[2] = scl[(i+2)*out_neurons + n];
        temp[3] = scl[(i+3)*out_neurons + n];

        // load input neurons sequentially
        in = _mm_load_ps(&src[i]);

        // load weights
        ws = _mm_load_ps(p);

        // dot product
        dp = _mm_dp_ps(in, ws, 0xff);

        // accumulator
        accum += dp.m128_f32[0]; 
    }
    // save the final result
    dst[n] = accum.m128_f32[0];
}

Это работает, но ускорение далеко от того, что я ожидал.Как вы можете видеть ниже, сверточный слой с х24 большим количеством операций, чем мой пользовательский слой точечного продукта, занимает меньше времени.Это не имеет смысла, и должно быть гораздо больше возможностей для улучшений.Каковы мои основные ошибки при попытке использовать AVX?(Я новичок в программировании AVX, поэтому я не совсем понимаю, с чего начать, чтобы полностью оптимизировать код).

**Convolutional Convolutional Layer Fully Optimized (AVX)**
Layer: CONV3-32 
Input: 28x28x32 = 25K   
Weights: (3*3*32)*32 = 9K   
Number of MACs: 3*3*27*27*32*32 = 7M    
Execution Time on OpenVINO framework: 0.049 ms

**My Custom Dot Product Layer Far From Optimized (AVX)**
Layer: FC
Inputs: 1x1x512
Outputs: 576    
Weights: 3*3*64*512 = 295K  
Number of MACs: 295K    
Execution Time on OpenVINO framework: 0.197 ms

Заранее спасибо за помощь!

1 Ответ

3 голосов
/ 24 сентября 2019

Приложение: То, что вы делаете, на самом деле является продуктом Matrix-Vector.Хорошо понятно, как реализовать это эффективно (хотя с кешированием и параллелизмом на уровне команд это не совсем тривиально).Оставшаяся часть ответа просто показывает очень простую векторизованную реализацию.


Вы можете существенно упростить свою реализацию, увеличив значения n+=8 и i+=1 (предполагая, что out_neurons кратно 8, в противном случае,необходимо выполнить некоторую специальную обработку для последних элементов), т. е. вы можете накапливать 8 dst значений одновременно.

Очень простая реализация, предполагающая наличие FMA (в противном случае используйте умножение и сложение):

void dot_product(const float* src, const float* scl, float* dst,
                 const int in_neurons, const int out_neurons)
{
    for(size_t n = 0; n < out_neurons; n+=8){
        __m256 accum = _mm256_setzero_ps();

        for(size_t i = 0; i < in_neurons; i++){
            accum = _mm256_fmadd_ps(_mm256_loadu_ps(&scl[i*out_neurons+n]), _mm256_set1_ps(src[i]), accum);
        }
        // save the result
        _mm256_storeu_ps(dst+n ,accum);
    }
}

Это все еще можно оптимизировать, например, путем накопления 2, 4 или 8 dst пакетов во внутреннем цикле, что не только сохранит некоторые широковещательные операции (инструкция _mm256_set1_ps), но также компенсируетзадержки инструкции FMA.

Godbolt-Link, если вы хотите поиграться с кодом: https://godbolt.org/z/mm-YHi

...