Ускорение нейронной сети GPU

Я пытаюсь реализовать нейронную сеть для работы на GPU с использованием библиотек Thrust и CUBLAS, но у меня много проблем с тем, чтобы она работала быстрее, чем наша текущая многопоточная и векторизованная реализация ЦП. В сети есть один скрытый слой с логистическими единицами и выходной слой с линейными единицами, и вот код для этого:

// Functor to add bias before computing logistic
template <typename T>
struct bias_logistic_f {
        __host__ __device__
        T operator()(const T& x, const T& y) const {
                return 1/(1+exp(-(x+y)));
        }
};
bias_logistic_f bias_logistic();

// Thrust vectors for input/hidden/output units
thrust::device_vector<FLT> batch(batch_rows*ndim);
thrust::device_vector<FLT> hid(batch_rows*nhid);
thrust::device_vector<FLT> gpu_code(ndata*ncode);

// ...Load data and network weights...

// Multiply input (batch) by weights (vis2hid)
// Our matrices are stored row-major, but BLAS wants column-major,
// so pretend they're transposed and compute hid' = vis2hid' * batch'
cublasDgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, nhid, batch_rows, ndim,
            &alpha, thrust::raw_pointer_cast(&vis2hid[0]), nhid,
                    thrust::raw_pointer_cast(&batch[0]), ndim,
             &beta, thrust::raw_pointer_cast(&hid[0]), nhid);

// Add hidbiases to hid and compute logistic
thrust::transform(hid.begin(), hid.end(), hidbiases.begin(), hid.begin(),
                  bias_logistic);

// Multiply hid by weights (hid2code)
cublasDgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, ncode, batch_rows, nhid,
            &alpha, thrust::raw_pointer_cast(&hid2code[0]), ncode,
                    thrust::raw_pointer_cast(&hid[0]), nhid,
             &beta, thrust::raw_pointer_cast(&gpu_code[b*batch_rows*ncode]), ncode);

// Add codebiases
thrust::transform(gpu_code.begin() + b*batch_rows*ncode, gpu_code.begin() + (b+1)*batch_rows*ncode,
                  codebiases.begin(), gpu_code.begin() + b*batch_rows*ncode,
                  thrust::plus<FLT>());

Наши входные данные представляют собой разреженную матрицу с около 150000 строк и 6500 столбцов, в среднем около 100 ненулевых элементов на строку. Это слишком велико, чтобы хранить полную матрицу в виде плотной матрицы на графическом процессоре, поэтому я делаю цикл по разреженной матрице, расширяя партии по 1000 строк каждая для ввода в нейронную сеть:

for(int b=0; b<nbatch; ++b) {
    // Zero out batch b
    thrust::fill(batch.begin(), batch.end(), 0.0f);
    // batch_val contains the non-zero values for the current batch, batch_idx the indices within the batch,
    // and batch_ptr indexes into batch_val/batch_idx
    // This is like CSR format except instead of compressing rows, it's compressing submatrices of 1,000 rows
    thrust::scatter(batch_val.begin() + batch_ptr[b],
                    batch_val.begin() + batch_ptr[b+1],
                    batch_idx.begin() + batch_ptr[b],
                    batch.begin());

    // ...Input batch to network (shown above)...
}

Наша реализация CPU делает то же самое, используя векторы STL. Когда я запустил оба и сравнил их время выполнения, я был удивлен, обнаружив, что код графического процессора занимает в среднем около 38 секунд для обработки наших данных, в то время как код процессора занимает всего около 27 секунд. Возможно, что это связано с тем, что графическому процессору несколько лет (Tesla C1060), а сервер - более новая 24-ядерная машина. Но все же я бы подумал, что с тысячами доступных потоков это не будет на 50% медленнее.

Любые идеи, как я могу сделать этот код работать быстрее? Я новичок в программировании на GPU, поэтому я не знаю, что я могу делать неправильно. Есть ли более эффективный способ работы с разреженными матрицами, чем то, что я здесь делаю, например, использование библиотеки CUSPARSE? Или лучше было бы вообще забыть о библиотеках высокого уровня и просто написать свои собственные ядра в CUDA, чтобы объединить шаги умножения / логистики / сложения матриц?

0 ответов

Другие вопросы по тегам