@Pushunter

Нейросеть на Cuda работает медленно, как ускорить?

Прочитал в книге Т.Рашида "Make your own network" про простенькую нейронную сеть для распознавания цифр. Реализовал ее на питоне, затем решил написать ее на Cuda. Проблема в том, что она обучается в разы медленнее чем на питоне или просто на плюсах. Уже все перепробовал. Очень надеюсь на Вашу помощь.
P.S. В программировании я новичок, так что не судите слишком строго за стиль кода и возможно глупые ошибки.

class neuralNet {
	float input_nodes;
	float hidden_nodes;
	float output_nodes;
	float learning_grade;
	float* wih;
	float* who;
public:
	neuralNet(int in, int hid, int out, float lr) {
		input_nodes = in;
		hidden_nodes = hid;
		output_nodes = out;
		learning_grade = lr;

		wih = new float[hidden_nodes * input_nodes];
		who = new float[hidden_nodes * output_nodes];

		for (int i = 0; i<input_nodes*hidden_nodes; i++)
			wih[i] = static_cast <float> (rand()) / static_cast <float> (RAND_MAX) - 0.5;

		for (int i = 0; i<hidden_nodes*output_nodes; i++)
			who[i] = static_cast <float> (rand()) / static_cast <float> (RAND_MAX) - 0.5;
	}
	~neuralNet() {
		delete[] wih;
		delete[] who;
	}
	float*activation_function(float* a, int n) {
		float* tmp = new float[n];
		for (int i = 0; i < n; i++)
			tmp[i] = 1 / (1 + exp(-a[i]));
		return tmp;
	}
	void train(float*inputs, float*targets) {

		//==================================  СЧИТАЕМ QUERY  =========================================

		cublasStatus_t stat; // CUBLAS functions status
		cublasHandle_t handle; // CUBLAS context

		float *hid_in, *hid_out, *in_out, *out_out;
		float * wih_d, *inputs_d, *hid_in_d, *in_out_d, *who_d, *hid_out_d;

		hid_in = new float[hidden_nodes];
		in_out = new float[output_nodes];

		// выделяем память на GPU сначала для того, чтобы посчитать hid_in
		cudaMalloc((float**)& hid_in_d, hidden_nodes * sizeof(float));
		cudaMalloc((float**)& inputs_d, input_nodes * sizeof(float));
		cudaMalloc((float**)& wih_d, input_nodes *hidden_nodes * sizeof(float));
		// выделяем память на GPU  для того, чтобы посчитать in_out
		cudaMalloc((float**)& in_out_d, output_nodes * sizeof(float));
		cudaMalloc((float**)& who_d, output_nodes *hidden_nodes * sizeof(float));
		cudaMalloc((float**)& hid_out_d, hidden_nodes * sizeof(float));

		// Показываем компилятору, сколько строк и сколько столбцов, чтобы он правильно умножал
		stat = cublasSetMatrix(hidden_nodes, input_nodes, sizeof(*wih), wih, hidden_nodes, wih_d, hidden_nodes);
		stat = cublasSetMatrix(input_nodes, 1, sizeof(*inputs), inputs, input_nodes, inputs_d, input_nodes);
		stat = cublasSetMatrix(hidden_nodes, 1, sizeof(*hid_in), hid_in, hidden_nodes, hid_in_d, hidden_nodes);
		// Аналогично
		stat = cublasSetMatrix(output_nodes, hidden_nodes, sizeof(*who), who, output_nodes, who_d, output_nodes);
		stat = cublasSetMatrix(output_nodes, 1, sizeof(*in_out), in_out, output_nodes, in_out_d, output_nodes);
		//

		stat = cublasCreate(&handle);
		// константы С1 и С2 - c1*[a]*[b] + c2*[c]
		float al = 1.0f;
		float bet = 0.0f;
		// Умножаем WIH на Inputs
		stat = cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, hidden_nodes, 1, input_nodes, &al, wih_d,
			hidden_nodes, inputs_d, input_nodes, &bet, hid_in_d, hidden_nodes);
		// Запись результат в hid_in
		stat = cublasGetMatrix(hidden_nodes, 1, sizeof(*hid_in), hid_in_d, hidden_nodes, hid_in, hidden_nodes);

		// С помощью функции активации делаем пересчет hid_out
		hid_out = activation_function(hid_in, hidden_nodes);

		// Заносим hid_out на GPU и делаем его условно матрицей, указывая столбцы и строчки...
		stat = cublasSetMatrix(hidden_nodes, 1, sizeof(*hid_out), hid_out, hidden_nodes, hid_out_d, hidden_nodes);

		// Умножаем  WHO на hid_out
		stat = cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, output_nodes, 1, hidden_nodes, &al, who_d,
			output_nodes, hid_out_d, hidden_nodes, &bet, in_out_d, output_nodes);

		// Записываем результат в in_out
		stat = cublasGetMatrix(output_nodes, 1, sizeof(*in_out), in_out_d, output_nodes, in_out, output_nodes);

		out_out = activation_function(in_out, output_nodes);

		//==================================  СЧИТАЕМ ERROR1  =========================================

		float * error1;

		// Считаем ошибки на выходе(здесь не обязателен GPU, т.к. всего 10 выходов)
		error1 = new float[output_nodes];
		for (int i = 0; i < output_nodes; i++)
			error1[i] = targets[i] - out_out[i];

		//==================================  СЧИТАЕМ ERROR2  =========================================

		float* error2;
		error2 = new float[hidden_nodes];

		float*who_t_h;  // transposed on CPU
		float *who_t_d; // transposed on GPU

		who_t_h = new float[hidden_nodes*output_nodes];

		cudaMalloc((void**)&who_t_d, output_nodes*hidden_nodes * sizeof(float));

		// Транспонируем матрицу WHO
		dim3 Grid(output_nodes / BLOCK_DIM, hidden_nodes / BLOCK_DIM);
		dim3 Block(BLOCK_DIM, BLOCK_DIM);
		transposeMatrixFast << <Grid, Block >> > (who_d, who_t_d, output_nodes, hidden_nodes);

		// Записываем значение транспонированной матрицы в who_t_h
		cudaMemcpy(who_t_h, who_t_d, output_nodes*hidden_nodes * sizeof(float), cudaMemcpyDeviceToHost);

		//__________________________________  WHO(TRANSP) * ERROR1  _________________________________________

		float *error1_d, *error2_d;

		// Начала выделим память для GPU пер-х
		cudaMalloc((void**)&error1_d, output_nodes * sizeof(float));
		cudaMalloc((void**)&error2_d, hidden_nodes * sizeof(float));

		// Покажем компилятору, сколько строчек и сколько столбцов в матрицах
		stat = cublasSetMatrix(output_nodes, 1, sizeof(*error1), error1, output_nodes, error1_d, output_nodes);
		stat = cublasSetMatrix(hidden_nodes, output_nodes, sizeof(*who_t_h), who_t_h, hidden_nodes, who_t_d, hidden_nodes);
		stat = cublasSetMatrix(hidden_nodes, 1, sizeof(*error2), error2, hidden_nodes, error2_d, hidden_nodes);

		// Перемножаем  who_t_h и error1
		stat = cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, hidden_nodes, 1, output_nodes, &al, who_t_d,
			hidden_nodes, error1_d, output_nodes, &bet, error2_d, hidden_nodes);

		// Перекидываем результат в error2
		stat = cublasGetMatrix(hidden_nodes, 1, sizeof(*error2), error2_d, hidden_nodes, error2, hidden_nodes);

		//==================================  СЧИТАЕМ WHO  =========================================

		float*left_part = new float[output_nodes];
		for (int i = 0; i < output_nodes; i++)
			left_part[i] = learning_grade*(error1[i] * out_out[i] * (1 - out_out[i]));

		float *left_part_d, *who_dd;
		// Выделяем память на GPU для дальнейшего подсчено new WHO
		cudaMalloc((void**)&left_part_d, output_nodes * sizeof(float));
		cudaMalloc((void**)&who_dd, hidden_nodes*output_nodes * sizeof(float));
		// Сообщаем компилятору вид матриц
		stat = cublasSetMatrix(output_nodes, 1, sizeof(*error1), left_part, output_nodes, left_part_d, output_nodes);
		stat = cublasSetMatrix(output_nodes, hidden_nodes, sizeof(*who), who, output_nodes, who_dd, output_nodes);
		stat = cublasSetMatrix(1, hidden_nodes, sizeof(*hid_out), hid_out, 1, hid_out_d, 1); // already transposed

		// Перемножаем и получаем new WHO
		stat = cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, output_nodes, hidden_nodes, 1, &al, left_part_d,
			output_nodes, hid_out_d, 1, &bet, who_dd, output_nodes);
		
		float* temporary;
		cudaMalloc((void**)&temporary, hidden_nodes*output_nodes * sizeof(float));
		cudaMemcpy(temporary, who, hidden_nodes*output_nodes * sizeof(float), cudaMemcpyHostToDevice);
		sum << <(output_nodes*hidden_nodes + 127) / 128, 128 >> > (temporary, who_dd, output_nodes*hidden_nodes);                // Может здесь возможно ускорить 
		// Копируем новые веса на CPU
		cudaMemcpy(who, temporary, output_nodes*hidden_nodes * sizeof(float), cudaMemcpyDeviceToHost);              
		cudaFree(temporary);																						
																													
		//==================================  СЧИТАЕМ WIH  =========================================

		float *left_part2 = new float[hidden_nodes];
		float*left_part2_d;
		cudaMalloc((void**)&left_part2_d, hidden_nodes * sizeof(float));
		special << <(hidden_nodes + 127) / 128, 128 >> > (left_part2_d, error2_d, hid_out_d, hidden_nodes, learning_grade);

		float  *wih_dd;

		// Выделяем память на GPU для дальнейшего подсчено new WHO
		cudaMalloc((void**)&wih_dd, hidden_nodes*input_nodes * sizeof(float));
		// Сообщаем компилятору вид матриц
		stat = cublasSetMatrix(hidden_nodes, input_nodes, sizeof(*wih), wih, hidden_nodes, wih_dd, hidden_nodes);
		stat = cublasSetMatrix(1, input_nodes, sizeof(*inputs), inputs, 1, inputs_d, 1); // already transposed

		// Перемножаем и получаем new WHO
		stat = cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, hidden_nodes, input_nodes, 1, &al, left_part2_d,
			hidden_nodes, inputs_d, 1, &bet, wih_dd, hidden_nodes);
		
		float* temporary1;
		cudaMalloc((void**)&temporary1, hidden_nodes*input_nodes * sizeof(float));
		cudaMemcpy(temporary1, wih, hidden_nodes*input_nodes * sizeof(float), cudaMemcpyHostToDevice);
		sum << <(input_nodes*hidden_nodes + 127) / 128, 128 >> > (temporary1, wih_dd, input_nodes*hidden_nodes);
		// Копируем новые веса на CPU
		cudaMemcpy(wih, temporary1, input_nodes*hidden_nodes * sizeof(float), cudaMemcpyDeviceToHost);
		cudaFree(temporary1);

		//==================================  ОЧИЩАЕМ ПАМЯТЬ  =========================================

		cudaFree(left_part2_d);
		cudaFree(wih_dd);
		cudaFree(who_dd);
		delete[] left_part2;
		//
		cudaFree(who_t_d);
		//
		cudaFree(error1_d);
		cudaFree(error2_d);
		// Подчищаем за собой
		cudaFree(hid_in_d);
		cudaFree(inputs_d);
		cudaFree(wih_d);
		// ... 
		cudaFree(in_out_d);
		cudaFree(who_d);
		cudaFree(hid_out_d);
		//... 
		cudaFree(left_part_d);
		delete[] left_part;
		//
		delete[] hid_in;
		delete[] in_out;
		delete[] hid_out;
		delete[] out_out;

		delete[] error1;
		delete[] error2;

		cublasDestroy(handle);
	}
  • Вопрос задан
  • 165 просмотров
Пригласить эксперта
Ответы на вопрос 2
dimonchik2013
@dimonchik2013
non progredi est regredi
хорошо бы профилирование
Ответ написан
Комментировать
@Pushunter Автор вопроса
Итак, решил проверить, сколько времени занимает выделение памяти на устройстве и копирование туда элементов... отдельно написал прогу, чтобы сравнить скорость перемножения матриц на ЦПУ и на ГПУ. Чисто перемножение на на ГПУ 0.07 ms, а на ЦПУ 0.001 s. Но выделение, копирование туда и обратно и само перемножение занимает 300 ms на ГПУ. Просто ШОК!!! Стал проверять отдельно каждую строчку и выяснилось...
stat = cublasCreate(&handle); эта строчка есть 300 ms. Я пока еще не придумал, как это починить, но благо нашел, что ест столько скорости...
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы