Skip to content

设计哲学

核心原则

1. 内存带宽感知

SpMV 本质上是内存受限的。我们的设计优先考虑:

关键洞察:在现代 GPU 上,内存带宽是瓶颈。我们的内核设计旨在最大化内存吞吐量,而非计算吞吐量。

2. 自适应计算

没有单一内核对所有矩阵最优。我们的自适应选择基于:

矩阵特征最优内核选择条件
avg_nnz < 4Scalar CSR每行并行度低
均匀分布Vector CSR一致的 warp 利用率
高偏度Merge Path完美工作分区
ELL 可转换ELL Kernel合并内存访问

选择算法

cpp
SpMVKernel select_kernel(const CSRMatrix* csr) {
    double avg_nnz = (double)csr->nnz / csr->num_rows;
    
    if (avg_nnz < 4.0) {
        return KERNEL_SCALAR_CSR;  // 低并行度
    }
    
    double skewness = compute_skewness(csr);
    
    if (skewness < 10.0) {
        return KERNEL_VECTOR_CSR;  // 均匀行
    }
    
    return KERNEL_MERGE_PATH;      // 不规则模式
}

3. 极简治理

项目现在优先控制维护面:

  • 对外 API 只保留核心 SpMV 能力。
  • 把验证放进测试和示例,而不是并行维护一套流程框架。
  • 不再把展示型模块直接塞进库本体。

内核设计权衡

Scalar CSR vs Vector CSR

方面Scalar CSRVector CSR
并行度每行一线程每行一 warp
内存访问非合并部分合并
适用场景极稀疏矩阵均匀稀疏
开销

Merge Path 算法

Merge Path 算法为不规则矩阵提供 完美负载均衡

ELL 格式

对于行长度均匀的矩阵,ELL 格式实现 完全合并访存

列主序布局:
values[k * num_rows + i] = A[i][col[k]]

访存模式:
线程 i 读取 values[0..num_cols-1] * num_rows + i
→ 连续线程访问连续内存

错误处理哲学

我们使用 语义化错误码 而非异常:

cpp
typedef enum {
    SPMV_SUCCESS = 0,
    SPMV_ERROR_NULL_POINTER,
    SPMV_ERROR_INVALID_DIMENSIONS,
    SPMV_ERROR_CUDA_MALLOC,
    SPMV_ERROR_CUDA_MEMCPY,
    // ...
} SpMVError;

优势:

  • 性能:无异常开销
  • 互操作性:C 兼容 API
  • 调试:显式错误传播

RAII 资源管理

所有 GPU 资源通过 CudaBuffer<T> 管理:

cpp
template<typename T>
class CudaBuffer {
public:
    CudaBuffer(size_t size);
    ~CudaBuffer();  // 自动 cudaFree
    
    T* device_ptr();
    void copy_from_host(const T* src);
    void copy_to_host(T* dst);
    
private:
    T* d_ptr_;
    size_t size_;
};

这确保即使在错误路径也 无内存泄漏

MIT License