GPU内核实现(上)

GPU内核实现
以下是基于CSR和ELLPACK格式的一些标准SpMV实现。
1. 标量CSR内核
GPU加速SpMV的最简单实现之一是标量内核方法。标量内核分配一个线程来处理SpMV中的每个稀疏点积。稀疏点积由每个线程以顺序方式处理,从而消除了对需要共享内存和/或扭曲级别降低的更高级技术的需求。以下代码片段显示了标量CSR内核的直接实现
__global__ void scalar_csr_kernel(const int m,
                                  const int *__restrict__ row_offsets,
                                  const int *__restrict__ cols,
                                  const double *__restrict__ vals,
                                  const double *__restrict__ x,
                                  double *__restrict__ y,
                                  const double alpha,
                                  const double beta)
{
  const int row = threadIdx.x + blockDim.x * blockIdx.x;
  if (row < m) {
    // 确定每一行的开始和结束
    int p = row_offsets[row];
    int q = row_offsets[row+1];
 
    // 运行全稀疏行*向量点积运算
    double sum = 0;
    for (int i = p; i < q; i++) {
      sum += vals[i] * x[cols[i]];
    }
    // 写到内存
    if (beta == 0) {
      y[row] = alpha * sum;
    } else {
      y[row] = alpha * sum + beta * y[row];
    }
  }
}
void scalar_csr(int m,
                int threads_per_block,
                int * row_offsets,
                int * cols,
                double * vals,
                double * x,
                double * y,
                double alpha,
                double beta)
{
  int num_blocks = (m + threads_per_block - 1) / threads_per_block;
  dim3 grid(num_blocks, 1, 1);
  dim3 block(threads_per_block, 1, 1);
  scalar_csr_kernel<<<grid, block>>>(m, row_offsets, cols, vals, x, y, alpha, beta);
}
如上所述,计算仅在行之间并行化。设

 每个块中的线程数为1,则完成计算所需的独立块数通过天花板函数计算为:

 (10-1)

在这种情况下,

 是一个可调参数,可以使用它来最大限度地提高此实现的性能。

1)根据线程和块的简单映射计算 ROW。
2)确保不会读取超出行偏移量数组中的任何数组边界。
3)确定要通过相邻读取行偏移量数组来加载的列(以及向量)索引和矩阵值。
4)对稀疏点积进行标量计算。
5)将结果写入内存。
在最后一步中,可以通过测试向量是否被覆盖或更新来节省一些内存带宽。
标量CSR实现,将在最广泛的稀疏矩阵范围内实现平庸的性能。然而,在某些情况下,这种内核可能会适度有效,即每行非零的平均数量很小。事实上,当每行非零的平均数量很高时,将需要多个请求来为内存事务提供服务,并且合并将是最小的。相比之下,当每行非零的平均数量非常低时,高内存带宽可以与此内核的简单性相结合,以实现良好的性能。
2. 向量CSR内核
GPU加速的SpMV最通用的高性能实现是向量核方法。与标量核不同,向量核在扭曲中使用warp多个线程来减少每个稀疏点积。它需要共享内存或扭曲洗牌操作来减少跨线程计算的部分减少的值。用于选择分配给每个稀疏行向量乘积的每个扭曲的线程数的启发式方法是基于

 。通常,通过找到小于或等于2的最小幂来找到最佳性能

 。此时,研究一个代码示例以检查实现的细节是有帮助的。这里关注的是只使用扭曲洗牌操作来完成稀疏点积的情况。应当注意,每个输出结果可以通过不超过波阵面大小的线程计算,在MI200架构上为64。当每行非零的平均数量远大于波前尺寸时,这可能会限制性能。

template <int THREADS_PER_ROW>
__global__ void vector_csr_kernel(const int m,
                                  const int *__restrict__ row_offsets,
                                  const int *__restrict__ cols,
                                  const double *__restrict__ vals,
                                  const double *__restrict__ x,
                                  double *__restrict__ y,
                                  const double alpha,
                                  const double beta)
{
  const int row = threadIdx.y + blockDim.y * blockIdx.x;
  if (row < m) {
    // 确定每一行的开始和结束
    int p = row_offsets[row];
    int q = row_offsets[row+1];
    // 开始稀疏行*向量点积运算
    double sum = 0;
    for (int i = p + threadIdx.x; i < q; i += THREADS_PER_ROW) {
      sum += vals[i] * x[cols[i]];
    }
    // 结束稀疏行*向量点积运算
#pragma unroll
    for (int i = THREADS_PER_ROW >> 1; i > 0; i >>= 1)
      sum += __shfl_down(sum, i, THREADS_PER_ROW);
    // 写到内存
    if (!threadIdx.x) {
      if (beta == 0) {
        y[row] = alpha * sum;
      } else {
        y[row] = alpha * sum + beta * y[row];
      }
    }
  }
}
void vector_csr(int m,
                int threads_per_block,
                int warpSize,
                int * row_offsets,
                int * cols,
                double * vals,
                double * x,
                double * y,
                double alpha,
                double beta)
{
  int nnz_per_row = nnz / m;
  int threads_per_row = prevPowerOf2(nnz_per_row);
  // 将每行的线程数限制为不大于波前(扭曲)大小
  threads_per_row = threads_per_row > warpSize ? warpSize: 
threads_per_row;
  int rows_per_block = threads_per_block / threads_per_row;
  int num_blocks = (m + rows_per_block - 1) / rows_per_block;
  dim3 grid(num_blocks, 1, 1);
  dim3 block(threads_per_row, rows_per_block, 1);
  if (threads_per_row <= 2)
      vector_csr_kernel<2><<<grid, block>>>(m, row_offsets, cols, vals, x, y, alpha, beta);
  else if (threads_per_row <= 4)
      vector_csr_kernel<4><<<grid, block>>>(m, row_offsets, cols, vals, x, y, alpha, beta);
 ...
  else if (threads_per_row <= 32)
      vector_csr_kernel<32><<<grid, block>>>(m, row_offsets, cols, vals, x, y, alpha, beta);
  else
      vector_csr_kernel<64><<<grid, block>>>(m, row_offsets, cols, vals, x, y, alpha, beta);
}
此实现与标量实现有几个关键区别。用

 来构建一个二维线程块。

1)线程的X维度表示分配给每行的线程数。这是通过函数prevPowerOf2计算的,该函数计算小于或等于输入变量2的最小幂。
2)Y维度表示每个线程块的行数。
3)线程块的总数由每个块处理的行数决定。
模板用于启动内核,每行的线程数变为编译时常量。这样可以在循环展开期间优化归约算法。
内核的开头方式与标量内核类似,但有一个关键区别在于如何从 Block/Grid Launch 启发式方法计算行索引。使用线程索引的y分量和块大小来计算这个值。然后,稀疏点积被分解为两个步骤。
1)每个线程以模板参数的倍数在行上循环,计算稀疏点积的部分结果。
2)完成后,subwarp 将使用 向下shuffle操作,将值累加到每个波前中的线程0。
注意到稀疏点积步骤的任何一部分都缺乏同步步骤。这是由于将自己限制在至少与波阵面大小一样小的减小量上。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/907446.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

稀疏矩阵向量乘法介绍

稀疏矩阵向量乘法介绍 稀疏矩阵向量乘法(SpMV)是每个隐式稀疏线性代数求解器。从简单的 Krylov 算法到 multigrid 的算法性能方法在很大程度上取决于 SpMV 实现的速度。因为 SpMV 具有非常低的算术强度,定义为浮点操作数,则实现速度受内存带宽。最大化内存带宽的实现将实现…

推荐关注《AI芯片开发核心技术详解》(1)、《智能汽车传感器:原理设计应用》(2)、《TVM编译器原理与实践》(3)、《LLVM编译器原理与实践》(4)

4本书推荐《AI芯片开发核心技术详解》、《智能汽车传感器:原理设计应用》、《TVM编译器原理与实践》、《LLVM编译器原理与实践》由清华大学出版社资深编辑赵佳霓老师策划编辑的新书《AI芯片开发核心技术详解》已经出版,京东、淘宝天猫、当当等网上,相应陆陆续续可以购买。该…

Ollama+OneAPI+Open WebUI 搭建本地大模型

✅Ollama 安装 ✅极简安装 curl -fsSL https://ollama.com/install.sh | sh✅Docker 安装 ❗前提是已安装NVIDIA Container Toolkit # 拉取镜像 docker pull ollama/ollama# 启动容器 docker run -d --gpus=all -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/o…

洛谷 P1216 [IOI 1994] 数字三角形 Number Triangles (记忆化搜索)

记忆化搜索思路:经典的DP题,看题解大佬个个是状态转移方程...我就写个记忆化搜索吧,这个数据量,只dfs暴搜是过不去的,写完记忆化之后发现有个测试点T了,下载了一波测试点数据,发现全是0,那么初始化dp数组为-1就好了。AcCode: #include<bits/stdc++.h> using nam…

【软件】在Windows和Ubuntu上使用TFTP和NFS

在Windows和Ubuntu上使用TFTP和NFS 零、介绍 最近在玩Linux开发板,在开发的过程中发现需要用到tftp和nfs来帮助传输文件,故此记录如何使用这两种软件。 TFTP(Trivial File Transfer Protocol) :是一种简化的文件传输协议,设计用于在客户端和服务器之间快速传输文件。轻量…

FastAPI Pydantic动态调整Schema

title: FastAPI Pydantic动态调整Schema date: 2025/3/29 updated: 2025/3/29 author: cmdragon excerpt: Pydantic动态Schema支持运行时字段调整和环境变量控制,实现毫秒级配置生效。通过字段级动态注入和条件必填验证,灵活适应业务需求。多租户系统采用条件字段过滤实现数…

【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(4)

比赛链接 本文发布于博客园,会跟随补题进度实时更新,若您在其他平台阅读到此文,请前往博客园获取更好的阅读体验。 跳转链接:https://www.cnblogs.com/TianTianChaoFangDe/p/18799072 开题 + 补题情况 和前三场比起来前期的签到题发挥稳定了许多,没有被卡很久,不过 1001 …

详细介绍Mybatis的缓存机制

一、缓存机制 1、缓存概述 缓存:缓存就是一块内存空间,保存临时数据 作用:将数据源(数据库或者文件)中的数据读取出来存放到缓存中,再次获取时直接从缓存中获取,可以减少和数据库交互的次数,提升程序的性能 缓存适用: 适用于缓存的:经常查询但不经常修改的,数据的正…

JS梳理之es6异步async await 协程 迭代器

es6异步 promise 链式调用 是对回调炼狱的一种优化 这次梳理一下async await async function fetchData() {const response = await fetch(https://api.example.com/data);const data = await response.json();return data; }// async 声明这是一个异步函数 // await 会暂停函数…

百度云同步盘 登录失败【%d】【155010】

前言全局说明百度云同步盘在2016前后升级了一次,修改了接口,但是没有发布完整安装包,当时可以自动升级来解决,后来自动升级失效,就之能手动打补丁来解决了。详细解决过程:https://www.cnblogs.com/wutou/p/18799043一、说明 1.1 环境: Windows 11 家庭版 23H2 22631.3737二…

grpc实现Aop

创建项目服务端:微软官方自带的ASP.NET.Core.gRPC服务项目。 客户端:ASP.NET.Core.WebApi项目。 公共类库:主要为AOP自定义拦截器类。依赖包导入 客户端:Grpc.AspNetCore、Grpc.Core.Api、Grpc.Net.ClientFactory、Grpc.Tools。公共类库:Grpc.Core.Api公共类库项目配置 创…

Win 11 安装百度云同步盘

前言全局说明百度网盘最早出来叫“百度云管家”,可以上传下载东西,后来大概在2012年,百度云同步盘上线,后来将云管家和同步盘放到一起,叫百度云,也就是现在用的这个。下面介绍 如何在 Win11上用百度云同步盘一、说明 1.1 环境: Windows 11 家庭版 23H2 22631.3737二、下载…