【问题标题】:How to serialize sparse matrix in Armadillo and use with mpi implementation of boost?如何在犰狳中序列化稀疏矩阵并与提升的 mpi 实现一起使用?
【发布时间】:2021-07-19 20:56:59
【问题描述】:

我一直在尝试从armadillo cpp 库中序列化稀疏矩阵。我正在做一些大规模的数值计算,其中数据存储在一个稀疏矩阵中,我想使用 mpi(Boost 实现)收集它并对来自不同节点的矩阵求和。我现在陷入困境的是如何将稀疏矩阵从一个节点发送到其他节点。 Boost 建议发送用户定义的对象(在这种情况下为SpMat),它需要被序列化。

Boost 的documentation 提供了一个关于如何序列化用户定义类型的很好的教程,我可以序列化一些基本类。现在,犰狳的 SpMat 类对我来说理解和序列化非常复杂。

我遇到了几个问题和他们非常优雅的答案

  1. This answer 作者:Ryan Curtin,Armadillo 的合著者和 mlpack 的作者,展示了一种非常优雅的方式来序列化 Mat 类。
  2. This answer by sehe 展示了一种序列化稀疏矩阵的非常简单的方法。

使用第一个我可以 mpi::send 一个 Mat 类到通信器中的另一个节点,但使用后者我不能做到 mpi::send

这是改编自第二个链接的答案

#include <iostream>
#include <boost/serialization/complex.hpp>
#include <boost/serialization/split_member.hpp>
#include <fstream>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <armadillo>
#include <boost/mpi.hpp>
namespace mpi = boost::mpi;
using namespace std;
using namespace arma;

namespace boost { 
    namespace serialization {

        template<class Archive>
            void save(Archive & ar, const arma::sp_mat &t, unsigned) {
                ar & t.n_rows;
                ar & t.n_cols;
                for (auto it = t.begin(); it != t.end(); ++it) {
                    ar & it.row() & it.col() & *it;
                }
            }

        template<class Archive>
            void load(Archive & ar, arma::sp_mat &t, unsigned) {
                uint64_t r, c;
                ar & r;
                ar & c;
                t.set_size(r, c);
                for (auto it = t.begin(); it != t.end(); ++it) {
                    double v;
                    ar & r & c & v;
                    t(r, c) = v;
                }
            }
    }}
BOOST_SERIALIZATION_SPLIT_FREE(arma::sp_mat)

int main(int argc, char *argv[])
{
    mpi::environment env(argc, argv);
    mpi::communicator world;
    arma::mat C(3,3, arma::fill::randu);
    C(1,1) = 0; //example so that a few of the components are u
    C(1,2) = 0;
    C(0,0) = 0;
    C(2,1) = 0;
    C(2,0) = 0;
    sp_mat A;
    if(world.rank() == 0) 
    {
        A = arma::sp_mat(C);
    }

    broadcast(world,A,0);

    if(world.rank() ==1 ) cout << A << endl;

    return 0;
}

我是这样编译的

$ mpicxx -L ~/boost_1_73_0/stage/lib  -lboost_mpi -lboost_serialization -I ~/armadillo-9.900.1/include -DARMA_DONT_USE_WRAPPER -lblas -llapack serialize_arma_spmat.cpp -o serialize_arma_spmat

$ mpirun -np 2 serialize_arma_spmat
[matrix size: 3x3; n_nonzero: 0; density: 0%]

作为进程号。 2 没有打印出预期的A 矩阵。所以广播没有工作。

我无法尝试以 Ryan 的回答为基础,因为我无法理解 Armadillo 中“SpMat_Meat.hpp”中的稀疏矩阵实现,这与 Mat 类非常不同。

如何序列化boost中的稀疏矩阵,以便在boost::mpi中使用?

【问题讨论】:

    标签: c++ boost sparse-matrix armadillo


    【解决方案1】:

    我不想这么说,但那个 sehe 的回答只是有缺陷。感谢您找到它。

    问题在于它在序列化过程中没有存储非零单元的数量。哎呀。我不知道我在测试时如何忽略了这一点。

    (看起来我有几个版本,而且肯定是拼凑了一个未经过正确测试的 Frankenversion)。

    我还进行了一个测试,矩阵已被清除(因此,如果您反序列化为具有正确形状但不为空的实例,您最终不会得到新旧数据的混合。)

    已修复

    #include <armadillo>
    #include <boost/archive/binary_iarchive.hpp>
    #include <boost/archive/binary_oarchive.hpp>
    #include <boost/serialization/split_member.hpp>
    #include <fstream>
    #include <iostream>
    
    BOOST_SERIALIZATION_SPLIT_FREE(arma::sp_mat)
    
    namespace boost { namespace serialization {
    
        template<class Archive>
        void save(Archive & ar, const arma::sp_mat &t, unsigned) {
            ar & t.n_rows & t.n_cols & t.n_nonzero;
    
            for (auto it = t.begin(); it != t.end(); ++it) {
                ar & it.row() & it.col() & *it;
            }
        }
    
        template<class Archive>
        void load(Archive & ar, arma::sp_mat &t, unsigned) {
            uint64_t r, c, nz;
            ar & r & c & nz;
    
            t.zeros(r, c);
            while (nz--) {
                double v;
                ar & r & c & v;
                t(r, c) = v;
            }
        }
    }} // namespace boost::serialization
    
    int main() {
    
        arma::mat C(3, 3, arma::fill::randu);
        C(0, 0) = 0;
        C(1, 1) = 0; // example so that a few of the components are u
        C(1, 2) = 0;
        C(2, 0) = 0;
        C(2, 1) = 0;
    
        {
            arma::sp_mat const A = arma::sp_mat(C);
            assert(A.n_nonzero == 4);
    
            A.print("A: ");
            std::ofstream outputStream("bin.dat", std::ios::binary);
            boost::archive::binary_oarchive oa(outputStream);
            oa& A;
        }
    
        {
            std::ifstream inputStream("bin.dat", std::ios::binary);
            boost::archive::binary_iarchive ia(inputStream);
    
            arma::sp_mat B(3,3);
            B(0,0) = 77; // some old data should be cleared
    
            ia& B;
    
            B.print("B: ");
        }
    }
    

    现在可以正确打印

    A:
    [matrix size: 3x3; n_nonzero: 4; density: 44.44%]
    
         (1, 0)         0.2505
         (0, 1)         0.9467
         (0, 2)         0.2513
         (2, 2)         0.5206
    
    B:
    [matrix size: 3x3; n_nonzero: 4; density: 44.44%]
    
         (1, 0)         0.2505
         (0, 1)         0.9467
         (0, 2)         0.2513
         (2, 2)         0.5206
    

    【讨论】:

    • 这看起来仍然不正确。 .set_size() 成员函数不能保证清除现有矩阵。如果矩阵大小已经与请求的大小相同,则 .set_size() 什么也不做,您最终可能会得到不需要的数据。考虑改用.zeros(),它将元素显式设置为零。
    • @hbrerkere 如果您查看所示示例的行为,您会发现我完全测试了。我错过了什么? 但是,是的,zeros() 可能很好(没有阅读过那个文档)。 感谢您的文档链接,这是非常明确的。修复代码
    • 不错。有没有机会让它使用 Cereal 而不是 Boost,就像当前的 mlpack 一样?
    • @DirkEddelbuettel 我敢打赌我可以。但是我手边没有麦片。也许有人应该问(&回答)这个问题:)
    猜你喜欢
    • 2020-07-24
    • 2015-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-22
    相关资源
    最近更新 更多