Я прочитал несколько связанных сообщений здесь, и успешно использовал умножение матриц со строками с помощью cuBLAS:
A*B (с специализацией по столбцу) = B*A (с специализацией по строке)
Я пишу оболочку для этого, чтобы я мог передавать матрицы A и B с мажором по строкам и соответственно возвращать результаты.
cublasStatus_t gemm_float(cuDataF &out, cublasHandle_t &handle, const cuDataF &in1, int row1, int col1, cublasOperation_t trans1, const cuDataF &in2, int row2, int col2, cublasOperation_t trans2, float alpha, float beta) {
/*if (trans1 == CUBLAS_OP_N && trans2 == CUBLAS_OP_T) {
}*/
return cublasSgemm(handle, trans1, trans2,
col2, row1, col1,
&alpha,
in2.data(), col2,
in1.data(), col1,
&beta,
out.data(), col2);
}
Но теперь мой вопрос: если я хочу A*transpose(B) по специальности, как это сделать?
Я пытался выполнить математический вывод вручную, но ответ неверный.
Нравиться:
A(2x3) = {1,2,3,4,5,6} (row majored)
B(2x3) = {7,8,9,10,11,12} (row majored)
A*transpose(B)?
@RobertCrovella да, я прочитал ссылку, которую вы прикрепили. Таким образом, предоставленная мной функция может обрабатывать не только квадратные матрицы. Но как быть с «A*transpose(B)»? Есть какой-нибудь намек? Спасибо





Я буду использовать код здесь в качестве отправной точки, поскольку вы упомянули, что уже наблюдали это, и порядок аргументов в предложенном вами вызове cublas соответствует этому. Какие изменения необходимы?
Итак, ваша матрица A и матрица B, интерпретируемые как 2x3, таковы:
A B
1 2 3 7 8 9
4 5 6 10 11 12
Следовательно, transpose(B)=B' это:
B'
7 10
8 11
9 12
А продукт C=AxB' это:
C
50 68
122 167
Аналогично для аналогичного тестового примера 3x2 мы получаем:
C = A x B'
23 53 83 7 8 1 3 5
29 67 105 9 10 2 4 6
35 81 127 11 12
Кажется, это работает:
# cat t232.cu
#include <cuda.h>
#include <cuda_runtime.h>
#include <cublas_v2.h>
#include <iostream>
#include <vector>
extern void gpuAssert(cudaError_t code, const char *file, int line);
void cublasAssert(cublasStatus_t code, const char *file, int line);
// See below
#define gpuErrchk(ans) { gpuAssert((ans), __FILE__, __LINE__); }
#define cublasErrchk(ans) { cublasAssert((ans), __FILE__, __LINE__); }
std::vector<float> CUDA_mult_MAT_T(const std::vector<float> &data_1 , const uint64_t data_1_rows, const uint64_t data_1_columns,
const std::vector<float> &data_2 , const uint64_t data_2_rows, const uint64_t data_2_columns)
{
cublasHandle_t handle;
cublasErrchk(cublasCreate(&handle));
std::vector<float> result(data_1_rows * data_2_columns); //Vector holding the result of the multiplication
/*----------------------------------------------------------------------------------------------*/
float* GPU_data_1 = nullptr;
gpuErrchk(cudaMalloc((void**)&GPU_data_1 , data_1.size()*sizeof(float))); //Allocate memory on the GPU
gpuErrchk(cudaMemcpy(GPU_data_1, data_1.data(), data_1.size()*sizeof(float), cudaMemcpyHostToDevice)); //Copy data from data_1 to GPU_data_1
float* GPU_data_2 = nullptr;
gpuErrchk(cudaMalloc((void**)&GPU_data_2 ,data_2.size()*sizeof(float))); //Allocate memory on the GPU
gpuErrchk(cudaMemcpy(GPU_data_2, data_2.data(), data_2.size()*sizeof(float), cudaMemcpyHostToDevice));//Copy data from data_2 to GPU_data_2
float* GPU_result = nullptr;
gpuErrchk(cudaMalloc((void**)&GPU_result , result.size()*sizeof(float))); //Allocate memory on the GPU
/*----------------------------------------------------------------------------------------------*/
const float alpha = 1.f;
const float beta = 0.f;
cublasErrchk(
cublasSgemm(handle , CUBLAS_OP_T, CUBLAS_OP_N,
data_2_columns , data_1_rows ,data_1_columns,
&alpha , GPU_data_2 , data_2_rows,
GPU_data_1 , data_1_columns,
&beta , GPU_result , data_2_columns)
); //Perform multiplication
gpuErrchk(cudaMemcpy(result.data() , GPU_result , result.size() * sizeof(float) , cudaMemcpyDeviceToHost)); //Copy back to the vector 'result'
gpuErrchk(cudaFree(GPU_data_1)); //Free GPU memory
gpuErrchk(cudaFree(GPU_data_2)); //Free GPU memory
gpuErrchk(cudaFree(GPU_result)); //Free GPU memory
cublasErrchk(cublasDestroy_v2(handle));
return result;
}
int main()
{
const auto r1 = CUDA_mult_MAT_T({1 , 2 , 3 , 4 , 5 , 6} , 2 , 3 ,
{7 , 8 , 9 , 10 , 11 , 12} , 3 , 2);
/*
Product:
7 8
1 2 3 x 9 10
4 5 6 11 12
*/
for(const auto& value: r1){std::cout << value << " " ;}
std::cout << std::endl;
const auto r2 = CUDA_mult_MAT_T({7 , 8 , 9 , 10 , 11 , 12} , 3 , 2 ,
{1 , 2 , 3 , 4 , 5 , 6} , 2 , 3);
/*
Product:
7 8
9 10 x 1 2 3
11 12 4 5 6
*/
for(const auto& value: r2){std::cout << value << " " ;}
std::cout << std::endl;
return 0;
}
// Shamelessly stolen from https://stackoverflow.com/a/14038590
void gpuAssert(cudaError_t code, const char *file, int line)
{
if (code != cudaSuccess)
{
fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line);
exit(code);
}
}
void cublasAssert(cublasStatus_t code, const char *file, int line)
{
if (code != CUBLAS_STATUS_SUCCESS)
{
std::cerr << "CUBLAS error.\nError code: ";
switch(code)
{
case CUBLAS_STATUS_SUCCESS:{std::cerr << "CUBLAS_STATUS_SUCCESS."; break;}
case CUBLAS_STATUS_NOT_INITIALIZED:{std::cerr << "CUBLAS_STATUS_NOT_INITIALIZED."; break;}
case CUBLAS_STATUS_ALLOC_FAILED:{std::cerr << "CUBLAS_STATUS_ALLOC_FAILED."; break;}
case CUBLAS_STATUS_INVALID_VALUE:{std::cerr << "CUBLAS_STATUS_INVALID_VALUE."; break;}
case CUBLAS_STATUS_ARCH_MISMATCH:{std::cerr << "CUBLAS_STATUS_ARCH_MISMATCH."; break;}
case CUBLAS_STATUS_MAPPING_ERROR:{std::cerr << "CUBLAS_STATUS_MAPPING_ERROR."; break;}
case CUBLAS_STATUS_EXECUTION_FAILED:{std::cerr << "CUBLAS_STATUS_EXECUTION_FAILED."; break;}
case CUBLAS_STATUS_INTERNAL_ERROR:{std::cerr << "CUBLAS_STATUS_INTERNAL_ERROR."; break;}
case CUBLAS_STATUS_NOT_SUPPORTED:{std::cerr << "CUBLAS_STATUS_NOT_SUPPORTED."; break;}
case CUBLAS_STATUS_LICENSE_ERROR:{std::cerr << "CUBLAS_STATUS_LICENSE_ERROR."; break;}
default:{std::cerr << "<unknown>."; break;}
}
std::cerr << "\nFile: "<< file << "\n";
std::cerr << "Line: "<< line <<std::endl;
exit(EXIT_FAILURE);
}
}
# nvcc -o t232 t232.cu -lcublas
# compute-sanitizer ./t232
========= COMPUTE-SANITIZER
50 68 122 167
23 53 83 29 67 105 35 81 127
========= ERROR SUMMARY: 0 errors
#
Обратите внимание, что я оставил «входное» описание обеих матриц, чтобы оно соответствовало тому, как они фактически будут рассчитываться. В процедуре расчета единственные изменения, которые я сделал, заключались в изменении первого спецификатора транспонирования с «OP_N» на «OP_T» (поскольку вторая входная матрица отображается как первая расчетная матрица в этой рубрике/формулировке с основными строками), и я изменил ведущее измерение первой матрицы вычислений (вторая входная матрица — та, которая транспонируется) от data_2_columns до data_2_rows.
Вероятно, это наименьшее изменение по сравнению с прототипом, указанным в вопросе. Однако мы могли бы обойтись без всего этого и признать, что transpose(B), когда B является основным по строкам, эквивалентно рассмотрению B как основного по столбцу. Аналогично, если A является основным по строкам, то transpose(A) делает его эквивалентным по основным столбцам. Поэтому мы могли бы использовать «обычную» формулировку CUBLAS, но указав OP_T для A (и использовать B как есть, не меняя порядок B и A, как указано в вопросе).
Спасибо, это то, что мне нужно. Я застрял на этом странном API несколько дней. На самом деле они могут просто добавить оболочку для разработчика CPP.
Исходная формула, которую вы показали:
A*B (column majored) = B*A (row majored)работает только для квадратных матриц. Однако информация здесь (особенно информация, полученная от г-на Виттека) может использоваться для передачи входных данных в CUBLAS, чтобы работать с вводом по строкам.