Инверсия матрицы в OpenCL

Я пытаюсь ускорить некоторые вычисления с помощью OpenCL, и часть алгоритма состоит в инвертировании матрицы. Существует ли какая-либо библиотека с открытым исходным кодом или свободно доступный код для вычисления факторизации lu (lapack dgetrf и dgetri) матрицы или общей инверсии, написанной на OpenCL или CUDA? Матрица действительна и квадратна, но не имеет других особых свойств, кроме этого. До сих пор мне удалось найти только базовые реализации матрично-векторных операций blas на GPU.

Матрица довольно маленькая, всего около 60-100 строк и столбцов, поэтому она может быть вычислена быстрее на процессоре, но она используется как бы в середине алгоритма, поэтому мне пришлось бы перенести ее на хост, вычислить инверсию и затем передайте результат обратно на устройство, где он затем используется в гораздо более крупных вычислениях.


person buchtak    schedule 31.05.2010    source источник
comment
Следует также отметить, что обращение матрицы может быть дорогостоящей операцией, особенно для больших матриц, и очень часто существует альтернативный способ решения поставленной задачи. Разложение LU — это один строительный блок, который можно использовать, чтобы избежать обратного.   -  person Tom    schedule 01.06.2010


Ответы (6)


У меня нет реализации в Open CL, но оба "Числовые Рецепты" и Гила Стрэнга "Into to Applied Math" имеют замечательные объяснения, которые легко кодировать. «NR» имеет код C, который вы можете адаптировать.

вычислить обратное

Это неправильно. Вы не вычисляете обратное с разложением LU, вы разлагаете матрицу. Если бы вы хотели получить обратное, вам пришлось бы выполнить прямую обратную замену рядом единичных векторов. Это небольшое, но важное отличие.

person duffymo    schedule 31.05.2010

Посмотрите на ViennaCL: http://viennacl.sourceforge.net/

person Hal Finkel    schedule 14.07.2010
comment
Это то, что я хотел. Единственная загвоздка в том, что ядра для факторизации LU не используют поворот, поэтому их производительность может быть довольно низкой и численно нестабильной для некоторых входных данных. - person buchtak; 14.07.2010

Проверить КУЛА

http://www.culatools.com/ http://www.culatools.com/versions/basic

person Gautham Ganapathy    schedule 02.06.2010

Я знаю, что это немного поздно, но если вы пытаетесь выполнить какие-либо матричные вычисления на такой маленькой матрице (60-100 строк), то вычисления будут намного быстрее на ЦП, а не на графическом процессоре из-за время, необходимое для копирования информации из основной памяти в память графического процессора. Если вы хотите сделать это, я бы посоветовал изучить использование параллельного языка, такого как OpenMP или MPI, поскольку они позволят вам распараллелить код для ускорения вычислений на ЦП.

person th3n3wguy    schedule 09.08.2012

Я исчисляю до 2k x 2k на процессоре, используя многопоточность, используя собственную библиотеку, поэтому теперь это в 3,5-3,65 раза быстрее (зависит от размера матрицы), чем при использовании одного потока. Я использовал процессор Intel Xeon 3,5 ГГц E5-1620 v3 и 16 ГБ оперативной памяти. (К сожалению, я удалил старую версию, чтобы добавить точные значения, но если есть какой-то приоритет, я мог бы переписать ПО)

Это мой алгоритм обратной матрицы, с которым я сравнивал. (Правильно, как я сказал, сделав много тестов снова превосходным результатом):

/*
Uses 2D arrays.
Main routines are:
init_2Dvector() that initializes any 2d vector (can be uchar, char, int, float or double)
multiply_2Dvector()
inverse()
*/

#include<iostream>
#include <vector>
#include <stdlib.h>
#include <time.h>

using namespace std;

/*
void print_2Dvector(vector<vector<double> >& vec)
{
    size_t xmax, ymax;
    ymax = vec.size();
    xmax = vec[0].size(); 

    int x, y;
    for (y = 0; y < ymax; y++)
    {
        for (x = 0; x < xmax; x++)
            cout << vec[y][x] << " \t";
        cout << endl;
    }
}*/

void print_2Dvector(vector<vector<double> >& vec,char *format="%lg \t")
{
    size_t xmax, ymax;
    ymax = vec.size();
    xmax = vec[0].size();

    int x, y;
    for (y = 0; y < ymax; y++)
    {
        {
            for (x = 0; x < xmax; x++)
                printf(format, vec[y][x]);
        }
        cout << endl;
    }
}

//Resizes to y_dim,x_dim any kind of 2d array:
template<typename T>
void init_2Dvector(vector<vector<T> >& vec, size_t y_dim, size_t x_dim)
{
    vec.resize(y_dim);
    for (size_t i = 0; i < vec.size(); i++)
        vec[i].resize(x_dim);
}
//Returns vec1*vec2. vec1 and 2 are not touch
vector< vector<double> > multiply_2Dvector(vector< vector<double> > vec1, vector< vector<double> > vec2)
{
    size_t xmax, ymax;
    ymax = vec1.size();   
    xmax = vec1[0].size();
    vector< vector<double> > vec3;
    if ((ymax != vec2[0].size()) || (xmax != vec2.size()))
    {
        cout << "ERROR on dim2_multiply() dimensions of vec2 not corresponding with vec1 ones" << endl; getchar(); return{};//returns a null
    }
    init_2Dvector(vec3, ymax, ymax);
    cout << "dimensions of vec3=" << vec3.size() << " x " << vec3[0].size() << endl;
    double xx;
    for (int y = 0; y < ymax; y++)
        for (int x = 0; x < ymax; x++)
        {
            xx = 0.0;
            for (int t = 0; t < xmax; t++)
                xx += vec1[y][t] * vec2[t][x];
            vec3[y][x] = xx;
        }
    return vec3;//ok
}

//returns inverse of x2, x2 is not modified
vector< vector<double> > inverse(vector< vector<double> > x)
{
    if (x.size() != x[0].size())
    {
        cout << "ERROR on inverse() not square array" << endl; getchar(); return{};//returns a null
    }

    size_t dim = x.size();
    int i, j, ord;
    vector< vector<double> > y(dim,vector<double>(dim));//output
    //init_2Dvector(y, dim, dim);
    //1. Unity array y: 
    for (i = 0; i < dim; i++)
    {
        y[i][i] = 1.0;
        for (j = i+1; j < dim; j++)
        {
            y[i][j]= y[j][i] = 0.0;
        }
    }

    double diagon, coef;
    double *ptrx, *ptry, *ptrx2, *ptry2;
    for (ord = 0; ord<dim; ord++)
    {
        //2 Hacemos diagonal de x =1
        int i2;
        if (fabs(x[ord][ord])<1e-15) //Si el elemento diagonal es 0 sumamos una columna que no sea 0 el elemento correspondiente
        {
            for (i2 = ord + 1; i2<dim; i2++)
            {
                if (fabs(x[i2][ord])>1e-15) break;
            }
            if (i2 >= dim)
                return{};//error, returns null
            for (i = 0; i<dim; i++)//sumo la linea que no es 0 el de la misma fila de ord
            {
                x[ord][i] += x[i2][i];
                y[ord][i] += y[i2][i];
            }
        }
        diagon = 1.0/x[ord][ord];
        ptry = &y[ord][0];
        ptrx = &x[ord][0];
        for (i = 0; i < dim; i++)
        {
            *ptry++ *= diagon;
            *ptrx++ *= diagon;
        }

        //Hacemos '0' la columna ord salvo elemento diagonal:
        for (i = 0; i<dim; i++)//Empezamos por primera fila
        {
            if (i == ord) continue;
            coef = x[i][ord];//elemento ha hacer 0 
            if (fabs(coef)<1e-15) continue; //si es cero se evita
            ptry = &y[i][0];
            ptry2 = &y[ord][0];
            ptrx = &x[i][0];
            ptrx2 = &x[ord][0];
            for (j = 0; j < dim; j++)
            {
                *ptry++ = *ptry - coef * (*ptry2++);//1ª matriz
                *ptrx++ = *ptrx - coef * (*ptrx2++);//2ª matriz
            }
        }
    }//end ord
    return y;
}


void test_5_inverse()
{
    vector< vector<double> > vec1 = {
        {0,-5,0,7,33,11,-1},
        {72,0,-11,7,9,33,5 },
        {-13,31,-5,15,29,30,24 },
        {-24,9,8,-23,31,-12,4 },
        {-3,-22,4,-24,-5,27,-10 },
        {-10,-21,-16,-32,-11,20,14 },
        {5,30,13,-32,29,-13,-13 }
    };
    vector< vector<double> > vec2;
    vec2 = inverse(vec1);
    vector< vector<double> > vec3;
    vec3 = multiply_2Dvector(vec1, vec2);

    cout << "initial array (must be unmodified):" << endl;
    print_2Dvector(vec1);

    cout << "Must be diagon array:" << endl;
    print_2Dvector(vec3," %8.3lf");
    cout << endl;
}


void test_6_inverse(int dim)
{
    vector< vector<double> > vec1(dim, vector<double>(dim));
    for (int i=0;i<dim;i++)
        for (int j = 0; j < dim; j++)
        {
            vec1[i][j] = (-1.0 + 2.0*rand() / RAND_MAX) * 10000;
        }

    vector< vector<double> > vec2;
    double ini, end;
    ini = (double)clock();
    vec2 = inverse(vec1);
    end = (double)clock();
    cout << "Time inverse =" << (end - ini) / CLOCKS_PER_SEC << endl;
    vector< vector<double> > vec3;
    vec3 = multiply_2Dvector(vec1, vec2);

    cout << "initial array (must be unmodified):" << endl;
    //print_2Dvector(vec1);

    cout << "Must be diagon array:" << endl;
    //print_2Dvector(vec3, " %8.3lf");
    cout << endl;
}

int main()
{
    vector< vector<double> > vec1;
    init_2Dvector(vec1, 10, 5);    //size_t ymax = vec1.size(),xmax = vec1[0].size();
    //test_2_dimension_vector();
    //test_one_dimension_vector();
    test_5_inverse();
    test_6_inverse(1300);
    cout << endl << "=== END ===" << endl; getchar(); 
    return 1;
}
person mathengineer    schedule 10.07.2018
comment
Пожалуйста, измерьте и отредактируйте свой комментарий с использованием эталонного процессора, входных данных и т. д. - person Andrej Kesely; 10.07.2018
comment
Спасибо, дело сделано. ПРИМЕЧАНИЕ. (Я не могу комментировать решение cuda в посте ниже): если вы переводите этот код CUDA, позаботьтесь о том, чтобы большинство плат GPU не могли использовать локальные массивы › 256 элементов, поэтому 512 дадут вам ошибки. - person mathengineer; 11.07.2018

Первоначальный вопрос (сейчас ему 7 лет) фактически был решен 4 года спустя в документ, описывающий обращение матриц в CUDA на основе Gauss-Jordan. Он пытается распределить вычисления по разным потокам и дает подробные показатели производительности для матриц размером до 2048.

Хотя это и не OpenCL, общие идеи легко переносятся из CUDA.

person StarShine    schedule 03.01.2018
comment
Это очень хороший момент, но позаботьтесь об использовании двойных, а не поплавков (люди opencl и cuda обычно предпочитают использовать поплавки, чтобы удвоить емкость ядер cuda), потому что при добавлении небольших значений к более высоким с использованием поплавков накапливается больше ошибок, чем при использовании поплавки. Плавающие числа используют 8 цифр и двойные 16. Добавление 1 к 9-значному числу не изменяет значение с плавающей запятой, поэтому могут возникнуть неизвестные ошибки округления, включая те, которые вызывают ошибку деления на 0 определителя. - person mathengineer; 17.08.2018