Cálculo paralelo de grandes matrices de covarianza.

9

Necesitamos calcular matrices de covarianza con tamaños que varían de a . Tenemos acceso a GPU y clústeres, nos preguntamos cuál es el mejor enfoque paralelo para acelerar estos cálculos.100000 × 10000010000×10000100000×100000

Abre el camino
fuente
1
¿Espera particularidades para su matriz de covarianza? Por ejemplo, ¿gran número de correlaciones "cercanas a 0"?
Dr_Sam
no, no podemos esperar nada ahora
Abre el camino
Cual es tu k Es decir, cuánto dura cada vector de datos. ¿Ya son de media cero?
Max Hutchinson
no, no son de media cero, pueden tomar cualquier valor
Abre el camino
3
@flow: '' datos clínicos '' es la aplicación, pero no el uso. Mi pregunta fue: supongamos que tiene la matriz de covarianza, ¿qué va a hacer con ella (desde un punto de vista matemático)? La razón por la que pregunto es que, al final, siempre se calcula muy poco a partir de él, y si se tiene en cuenta esto, generalmente se pueden acelerar enormemente las cosas evitando calcular la matriz de covarianza completa mientras se obtiene el resultado posterior deseado.
Arnold Neumaier

Respuestas:

17

Lo primero es reconocer que puedes hacer esto usando BLAS. Si su matriz de datos es (cada x es un vector de columna correspondiente a una medida; las filas son pruebas), entonces puede escribir la covarianza como: C i j = E [ x i , x j ] - E [ x i ] E [ x j ] = 1X=[x1x2x3...]Rm×nX Podemos escribir esto como: C=1

Cyoj=mi[Xyo,Xj]-mi[Xyo]mi[Xj]=1nortekXyokXjk-1norte2(kXyok)(kXjk)
, donde(1T)es la fila-vector con todos los elementos 1, de modo(1TX)es un vector fila de las sumas de columna deX. Esto se puede escribir completamente como BLAS, dondeXTXes unGEMMo, mejor aún, unSYRK/HERKy puede obtener el(1TX)=bcon unGEMV,
C=1norteXTX-1norte2(1TX)T(1TX)
(1T)(1TX)XXTX(1TX)=si con GEMM o SYRK / HERK nuevamente, y los prefactores conSCAL.siTsi

Sus matrices de datos y resultados pueden ser de alrededor de 64 GB, por lo que no va a caber en un solo nodo o en el valor de un nodo de GPU. Para un clúster que no sea GPU, es posible que desee ver PBLAS , que se siente como scalapack. Para las GPU, las bibliotecas de múltiples nodos aún no están allí. Magma tiene algún tipo de implementación BLAS paralela subyacente, pero puede no ser fácil de usar. No creo que CULA tenga múltiples nodos todavía, pero es algo a lo que hay que prestarle atención. CUBLAS es de nodo único.

También le sugiero que considere implementar el paralelismo usted mismo, especialmente si está familiarizado con MPI y tiene que conectarlo a una base de código existente. De esa manera, puede cambiar fácilmente entre CPU y GPU BLAS y comenzar y terminar con los datos exactamente donde lo desee. No debería necesitar más que unas pocas llamadas MPI_ALLREDUCE .

Max Hutchinson
fuente
Gracias por su análisis y lista de funciones BLAS relevantes. Después de leer su respuesta, los he usado para acelerar y optimizar el cálculo de la matriz de covarianza en la versión de desarrollo de Scilab (www.scilab.org).
Stéphane Mottelet
mi[Xyo,Xj]mi[Xyo]mi[Xj]
1

Implementé la fórmula dada por @Max Hutchinson con CUBlas y Cuda Thrust y la comparé con las herramientas de cálculo de covarianza en línea. Parece que el mío produce buenos resultados. El siguiente código planeado para QDA Bayes. Entonces la matriz dada puede contener más de una clase. Entonces se calculan las matrices de covarianza múltiple. Espero que sea útil para alguien.

//! Calculates one or more than one coVarianceMatrix given data.
//  There can be many classes since many covariance matrixes.
/*!
    \param inMatrix This vector contains matrix data in major storage. 
    Forexample if inMatrix=[1 2 3 4 5 6] and trialSizes=[2] this means matrix we will work on a matrix like :
        |1 4 |
        |2 5 |
        |3 6 | -> 2 Trials, 3 Features. Columns contains feature rows contains trials (samples)
    \param trialSizes There can be many classes since many covariance matrixes. Samples from all classes will be given with inMatrix.
    But we need to know how many trials(samples) we have for each class. 
    For example if inMatrix=[1 2 3 4 5 6 7 8 9 10 11 12] and trialSizes=[2,2] 
    this means matrix we will work on a matrix like :
        |1 4 |  |7 10 |
        |2 5 |  |8 11 |
        |3 6 |  |9 12 |  --> Total number of trials(samples which is total rowCount) 2 + 2 = 4 , 
                             So colSize = inMatrix.size()/4 = 3(feature vector size)
                         --> There is two element in trialSize vec so each vector has to samples
*/
void multiQDACovianceCalculator(std::vector<float>& inMatrix, std::vector<int>& trialSizes)
{
    cublasHandle_t handle; // CUBLAS context
    int classCount = trialSizes.size();
    int rowSize = std::accumulate(trialSizes.begin(), trialSizes.end(), 0);
    int dimensionSize = inMatrix.size() / rowSize;
    float alpha = 1.0f;
    float beta = 0.0f; // bet =1

    thrust::device_vector<float> d_cov1(dimensionSize * dimensionSize);
    thrust::device_vector<float> d_cov2(dimensionSize * dimensionSize);
    thrust::device_vector<float> d_covResult(dimensionSize * dimensionSize);

    thrust::device_vector<float> d_wholeMatrix(inMatrix);
    thrust::device_vector<float> d_meansVec(dimensionSize); // rowVec of means of trials
    float *meanVecPtr = thrust::raw_pointer_cast(d_meansVec.data());
    float *device2DMatrixPtr = thrust::raw_pointer_cast(d_wholeMatrix.data());
    auto maxTrialNumber = *std::max_element(trialSizes.begin(), trialSizes.end());
    thrust::device_vector<float> deviceVector(maxTrialNumber, 1.0f);

    cublasCreate(&handle);
    // Inside of for loop  one covariance matrix calculated each time
    for (int i = 0; i < trialSizes.size(); i++)
    {
        // X*transpose(X) / N
        alpha = 1.0f / trialSizes[i];
        cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_T, dimensionSize, dimensionSize, trialSizes[i], &alpha,
            device2DMatrixPtr, dimensionSize, device2DMatrixPtr, dimensionSize, &beta,
            thrust::raw_pointer_cast(d_cov1.data()), dimensionSize);

        // Mean vector of each column
        alpha = 1.0f;
        cublasSgemv(handle, CUBLAS_OP_N, dimensionSize, trialSizes[i], &alpha, device2DMatrixPtr,
            dimensionSize, thrust::raw_pointer_cast(deviceVector.data()), 1, &beta, meanVecPtr, 1);

        // MeanVec * transpose(MeanVec) / N*N
        alpha = 1.0f / (trialSizes[i] * trialSizes[i]);
        cublasSgemm(handle, CUBLAS_OP_T, CUBLAS_OP_N, dimensionSize, dimensionSize, 1, &alpha,
            meanVecPtr, 1, meanVecPtr, 1, &beta,
            thrust::raw_pointer_cast(d_cov2.data()), dimensionSize);

        alpha = 1.0f;
        beta = -1.0f;
        //  (X*transpose(X) / N) -  (MeanVec * transpose(MeanVec) / N*N)
        cublasSgeam(handle, CUBLAS_OP_N, CUBLAS_OP_N, dimensionSize, dimensionSize, &alpha,
            thrust::raw_pointer_cast(d_cov1.data()), dimensionSize, &beta, thrust::raw_pointer_cast(d_cov2.data()), 
            dimensionSize, thrust::raw_pointer_cast(d_covResult.data()), dimensionSize);

        // Go to other class and calculate its covarianceMatrix
        device2DMatrixPtr += trialSizes[i] * dimensionSize;
    }
    printVector(d_covResult);
    cublasDestroy(handle);
}
Kadir Erdem Demir
fuente