早期为速度, 使用 cblas 比较多. cblas 非常不好用, double, float 函数各一 还好说, 关键是大部分函数的参数太多了, 如
void cblas_dgemm(const enum CBLAS_ORDER Order, const enum CBLAS_TRANSPOSE TransA, const enum CBLAS_TRANSPOSE TransB, const int M, const int N, const int K, const double alpha, const double *A, const int lda, const double *B, const int ldb, const double beta, double *C, const int ldc);基本上, 每次使用, 我都测试一下 M, N, K 几个值差得比较大的情形, 看看有没 有段错误, 这个方法比认真检查参数还要管用.
后来, 用 C++ 比较多, 仿照 STL, 希望尽量泛化, 不要 double, float 各写一 遍. 还希望进一步简化, 对所有矩阵, dense 的也好, csr, csc 也好, 我 们应该只需要写
gemm(A, B).
还要将计算和存储分开, 比如
gemm(A, B) 只表示一个表达式, 并不做任何实
际运算, 需要时, 可以将结果并行地存到另一个区域. 这样, 比如写
C=gemm(A, B) * alpha + beta * C 就比上面的 cblas 简明易懂多了. 还可以
用 C.parallel_assign(gemm(A, B) * alpha + beta * C) 表示并行地计算
$$C = \alpha A B + \beta C.$$
要想实现上面的表达式, 我们需要一个 proxy 抽象,
vector_proxy,
matrix_proxy. 矩阵, 向量的所有公有操作, 在 proxy 里实现. 具体的向量,
矩阵继承此 proxy 即可.
对所有
vector_proxy, 定义
template <class Derived, class Value, class Reference> struct vector_proxy { ... size_t size() const; value_type get(size_t idx) const; reference_type get(size_t idx); ... };其中,
Derived 为具体的向量, 需要定义 get, size, 并继承 vector_proxy<Derived>.
再为
vector_proxy 定义 加, 减 等基本运算, 结果也为一个 proxy, 此
proxy 的 get(size_t idx) 函数实现计算.
从 proxy 到 proxy 的赋值如下.
template <class Derived, class Value, class Reference> struct vector_proxy { ... template <class R> void assign(const vector_proxy<R> &r) { size_t n = size(); assert(r.size() == n); for (size_t i = 0; i < n; ++i) { get(i) = r.get(i); } } template <class R> void parallel_assign(const vector_proxy<R> &r) { size_t n = size(); assert(r.size() == n); #pragma omp parallel for for (size_t i = 0; i < n; ++i) { get(i) = r.get(i); } } ... }
matrix_proxy 和 vector_proxy 完全类似.
具体到
gemm_proxy 上, 如下实现 get 方法即可.
value_type get(size_t i, size_t j) { return vec_inner_prod(m_A.row(i), m_B.col(j)); }矩阵的
row, col 返回的也是 vector_proxy. 在 vec_inner_prod 里,
根据向量是 dense 还是 sparse, 采用不同的计算方法即可.
gemm_proxy 里的 row(i), col(j) 可以采用矩阵和向量的乘积
gemv(m_B, m_A.row(i)), gemv(m_A, m_B.col(j)). 在 gemv_proxy 里,
还是用 vec_inner_prod 来自动区分 dense/sparse 的向量.
gemm(A, B) 在 A, B 都是稀疏矩阵时, 情况比较复杂, 使 gemv 返回一个稀
疏向量的实现效率比较低, 我直接将结果当成 dense 了.
No comments:
Post a Comment