Some thoughts on linalg

2018/06/08

Tags: GSoC Shogun

So in the next two weeks I’m going to work on refactoring linalg of Shogun. The data types in Shogun, SGVector<T>, SGMatrix<T> are templated classes which maintain a piece of memory in either CPU or GPU and provide basic operations like array-like element access. linalg::, is the upper level module working with SGVector, SGMatrix, providing linalg algebra operations, like matrix additions / multiplications.

Though SGVector and SGMatrix, as template classes, are capable to represent different data types (float32_t, float64_t, etc.), most algorithms in Shogun actually do not behave generically. They accept CFeatures* as input but later expect them to be dense features with float64_t element type. This is most of the cases in Shogun.

There are ad-hoc solutions. For example, take a look at code of LDA.

bool LDA::train_machine(CFeatures* data)
{
    CDotFeatures* features;
    // ...
    if (features->get_feature_type() == F_SHORTREAL)
        return train_machine_templated<float32_t>();
    else if (features->get_feature_type() == F_DREAL)
        return train_machine_templated<float64_t>();
    else if (features->get_feature_type() == F_LONGREAL)
        return train_machine_templated<floatmax_t>(); 
}

We check the type in runtime and then call templated implementations. This solves the problem to some extent but we have to repeat similar things everywhere. And it does not make much sense to check the type in algorithm implementations since whether a specific data type is supported does not depend on the algorithm itself but the underlying mathematics operations used instead.

Another issue is linalg. Backed by Eigen or ViennaCL, linalg offers unified interface to backends. However, the number of interface in linalg is growing. Whenever we need a method in Eigen, say matrix decomposition, we add a new virtual method to linalg to wrap the function in the backend. In this way we actually break ABI compatibility.

Besides, since data can be in either CPU or GPU, linalg has to check where data lives every time because it lacks context of former calls. Of course setting a global context is not the best choice since this is not a thread-safe way.

So we are moving towards a un-templated type: Matrix, Vector, without the template argument T. Then everything will be checked in runtime. We will have Matrix implemented as something like

class Matrix
{
    void* data;
    Type type;
};

This is actually from the ongoing PR last year. However, in this case, we have to use a big switch to invoke the corresponding operation every time. This is much like the CPU/GPU checking problem above.

With these challenges in mind, in the upcoming weeks I will be working on some new design of linalg. Stay tuned for updates :)