深入理解warp shuffle

2023-11-16

warp shuffle

相关函数学习:

__shfl_up_sync(0xffffffff, lane_val, i)是CUDA函数之一,用于在线程束内的线程之间交换数据。其中:

  • 0xffffffff是掩码参数,指示线程束内所有线程都参与数据交换。一个32位无符号整数,用于确定哪些线程会参与数据交换。具体来说,若第 i i i 位为1,则第 i i i 个线程会参与交换,否则不参与。例如,若 mask 为 0x0f,则当前warp中的后四个线程会参与数据交换,前28个线程不参与。
  • lane_val是要交换的数据,即当前线程的值。
  • i是要向上移动的距离,即要将数据从当前线程的下一个线程移动到当前线程的位置。

__shfl_up_sync 函数会将当前线程 var 的值传递给与其向上 delta 个线程相隔的线程,而当前线程则接收到与其向上 delta 个线程相隔的线程的值,所以可以理解为该函数完成了两件事,一个是传递给下一个,另一个是该函数的值为被传递的值。
如果某个线程在 mask 中对应的位为0,则它不参与数据交换,即接收到的值为自身的 var 值。此外,如果向上移动 delta 个线程后,超出了当前warp的边界,则接收到的值为该线程的默认值(即0或者NaN)。

原理:
用下面这段代码做实验你会知道__shfl_down__syn函数的返回准则。

1、如果threadA,threadB,存在一个offset的距离,则一定是存到down的A中。
2、对于一个laneID,如果 A : l a n e I D − o f f s e t > = 0 A:laneID-offset >= 0 A:laneIDoffset>=0则该线程的值将存到前offset那个线程的位置上。
3、对于一个laneID,如果 B : l a n e I D + o f f s e t < W a r p S i z e B:laneID+offset < WarpSize B:laneID+offset<WarpSize 意味着当前线程可以接受后offset那个线程的值。
4、如果A式<0则意味着,当前线程前面没有offset个单位,则不需要给任何线程赋值。
5、如果B式>=,则意味着,当前线程没有可以接受的值,那么函数返回的结果就是当前线程的值
一张图理解:映射关系
在这里插入图片描述

#include <stdio.h>
__global__ void scan4() {
 
    int laneId = threadIdx.x & 0x1f;
    int val = 8 - laneId;

    for (int offset = 8 >> 1; offset > 0; offset >>= 1)
    {
       int n = __shfl_down_sync(0xff, val, offset, 8); 
       printf("Block id: %d Thread id :%d n value = %d\n", blockIdx.x, threadIdx.x, n);
    }
}
int main() {
    scan4<<< 2, 8 >>>();
    cudaDeviceSynchronize();
    return 0;
}

举个例子,假设当前线程束包含4个线程,它们的lane_id分别为0、1、2和3。
当调用__shfl_up_sync(0xffffffff, lane_val, 2)时,线程束内的线程会进行以下数据交换:

  • 线程0和线程2之间交换数据。
  • 线程1和线程3之间交换数据。

在早期的硬件上,只能通过使用共享内存,这就涉及将数据写入共享内存、同步,然后从共享内存中读取数据
Kepler 的 shuffle 指令 (SHFL) 使线程能够直接从同一个warp中(32 个线程)的另一个线程读取寄存器
用于在一个 warp(一个在单个处理器核上同步执行的线程组)内高效地重新排列数据。
Warp shuffle 允许 warp 内的线程互相交换数据,而不需要与 warp 外的线程通信,这可以显著降低通信的延迟和带宽需求。
同步发生在一个 warp 中并且隐含在指令中,因此不需要通过调用__syncthreads() 同步整个线程块。

1、warp shuffle 是对每个线程都有作用的。
2、但我们只需要关心我们关心的那部分
如图
在这里插入图片描述

// Sums `val` accross all threads in a warp.
//
// Assumptions:
//   - The size of each block should be a multiple of `warpSize`
template <typename T>
__inline__ __device__ T WarpReduceSum(T val) {
#pragma unroll
  for (int offset = (warpSize >> 1); offset > 0; offset >>= 1) {
    val += __shfl_down_sync(0xffffffff, val, offset, warpSize);
  }
  return val;
}

在 warp shuffle 算法中,使用二进制归约算法来实现在 warp 内快速计算数据的某些函数,例如前缀和、求最大值或最小值等。二进制归约算法的基本思想是,将相邻的两个元素两两配对,然后在每一轮迭代中将相邻元素的值累加,并重复这个过程,直到只剩下一个元素为止,这个元素即为最终的结果。因此,循环迭代的次数应该是以 2 为底数的对数,这也是为什么在循环中 i 是乘以 2 的原因。
在二进制归约中,我们希望每个线程将其值与距离其 2 的幂次方的线程的值相加。例如,在第一轮循环中,每个线程将其值与距离其 1 个位置的线程的值相加,第二轮循环中,每个线程将其值与距离其 2 个位置的线程的值相加,以此类推。
通过将 i 每次乘以 2,我们可以将循环次数减少到 l o g 2 ( w a r p S i z e ) log_2(warpSize) log2(warpSize),其中 warpSize 是 warp 的大小。

在这段代码中,每个 warp 中的线程为输入数组的一个元素计算其自己的前缀和值,然后使用 warp shuffle 与相邻的线程交换值,以执行二进制归约以计算整个 warp 的最终前缀和值。__shfl_up_sync() 函数用于与左侧相距 i 个位置的线程交换数据,if 语句确保只有 ID 大于等于 i 的线程会参与归约z(因为小于i的线程没有线程给他传数,他函数返回值为自己,最后实现的是自己加自己的操作,没有意义)最后,warp 中的最后一个线程将最终的前缀和值存储在 prefix_sum 变量中。

线程 0 将得到最终归约的结果 v。下面这段代码就是完整的基于 shfl_down 的 warp reduction 函数。
__shfl_up_sync
The __shfl_sync() intrinsics permit exchanging of a variable between threads within a warp without use of shared memory.

Copy from a lane with lower ID relative to caller 把低的复制给高的

__device__ void warp_prefix_sum(int val, int& prefix_sum) {
    int lane_id = threadIdx.x % warpSize;
    int lane_val = val;

    for (int i = 1; i < warpSize; i *= 2) { //外层枚举从1开始
        int neighbor_val = __shfl_up_sync(0xffffffff, lane_val, i);
        if (lane_id >= i) { // 因为前面的没必要更新,因为更新的数据都是没用的,而且是不对的,没人跟他换他函数返回值是自己,相当于自己加自己,没什么意义!
            lane_val += neighbor_val;
        }
    }

    if (lane_id == warpSize - 1) {
        prefix_sum = lane_val;
    }
}

__shfl_down()
注意,如果 warp 中的所有线程都想要最终的规约结果,您可以在 warp 中使用 __shfl_xor() 指令替换 __shfl_down(),如下所示。任何一个版本都可以在下一节的 block reduce 中使用。

__inline__ __device__
int warpAllReduceSum(int val) {
  for (int mask = warpSize/2; mask > 0; mask /= 2) 
    val += __shfl_xor(val, mask);
  return val;
}

实验

实验1:

#include <stdio.h>

__global__ void scan4() {
    int laneId = threadIdx.x & 0x1f;
    // Seed sample starting value (inverse of lane ID)
    int value = 31 - laneId;

    // Loop to accumulate scan within my partition.
    // Scan requires log2(n) == 3 steps for 8 threads
    // It works by an accumulated sum up the warp
    // by 1, 2, 4, 8 etc. steps.
    for (int i=1; i<=4; i*=2) {
        // We do the __shfl_sync unconditionally so that we
        // can read even from threads which won't do a
        // sum, and then conditionally assign the result.
        int n = __shfl_up_sync(0xffffffff, value, i, 8); // 8 圈定了每8个thread一组
        if ((laneId & 7) >= i)
            value += n;
    }

    printf("Thread %d final value = %d\n", threadIdx.x, value);
}

int main() {
    scan4<<< 1, 32 >>>();
    cudaDeviceSynchronize();

    return 0;
}

这个算法的原理是这么个情况,如果是up就规约到最高,down就规约到最低。
一组:在这里插入图片描述
二组:在这里插入图片描述

实验2:

#include <stdio.h>
__global__ void scan4() {
    int laneId = threadIdx.x & 0x1f;
    // Seed sample starting value (inverse of lane ID)
    int val = 8 - laneId;

    for (int i = 1; i <= 8 / 2; i ++ )
    {
       int n = __shfl_up_sync(0xff, val, i, 8); 
       if(laneId >= i) //加上 or 去除,体会不同
       {
        val += n;
        printf("Thread id :%d n value = %d\n val value: %d\n", threadIdx.x, n, val);
       }
    }
}
int main() {
    scan4<<< 1, 8 >>>();
    cudaDeviceSynchronize();
    return 0;
}

去除 i f ( l a n e I d > = i ) if(laneId >= i) if(laneId>=i)后的实验
在这里插入图片描述
为什么1号是8?是因为1作为0号节点的接收对象,0节点给他传了0的值,为8。
这个地方就体现了,为什么0号thread的n也是8?是因为,第一个for循环中,0号节点没有作为up来接受的对象,所以函数返回为他自己。

实验太重要了!!!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深入理解warp shuffle 的相关文章

随机推荐

  • git 使用经验

    常用命令 git stash 暂存当前已修改的文件 git stash pop 释放暂存的文件 git status 查看当前状态 git checkout master 切换到主分支 提交文件相关 git add 增加文件 git rm
  • lammps案例:液体平衡态rdf计算模拟练习

    分享一个2维液态平衡模拟案例代码 随机生成400 0 8个原子并进行温度初始化 原子在LJ力场作用下运动到一定位置后达到平衡 并输出rdf数据 代码已经注释 以供参考 当原子数量超过400 0 9时 体系接近固态 可自行更改参数并结合rdf
  • scrapy_redis配置redis集群

    单节点的redis太容易挂了 总是出现意外情况 借助scrapy redis sentinel库实现scrapy redis对redis集群的使用 按照官方库的说明去修改后启动项目会报错 报错的原因其实是作者的源码包中一个叫queue py
  • 修改Fedora 25与Windows 10的默认启动顺序

    首先贴出Fedora25下 boot grub2 grub cfg的内容 DO NOT EDIT THIS FILE It is automatically generated by grub2 mkconfig using templat
  • 看完这篇 教你玩转渗透测试靶机vulnhub——DC6

    Vulnhub靶机DC6渗透测试详解 Vulnhub靶机介绍 Vulnhub靶机下载 Vulnhub靶机安装 Vulnhub靶机漏洞详解 信息收集 暴力破解 漏洞发现 漏洞利用 nmap脚本提权 获取flag Vulnhub靶机渗透总结 V
  • Unity显示被遮挡的模型

    具体显示为这个效果 同事在网上找了一个受光的材质 Shader Custom RoleShader Properties Color Color Color 1 1 1 1 MainTex Albedo RGB 2D white Gloss
  • 一文彻底搞懂 MYSQL分库分表方案

    MYSQL分库分表方案 垂直分表 把一部分表字段放入一张表 另一部分放入其他的表 按照表字段的使用频次分门别类的划分 例如 在商品列表查询时 列表中只是展示部分字段 同时这个列表查询比详情信息查询更加高频 并不需要把所有字段都展示 我们可以
  • 可信执行环境(TEE):深入探讨安全计算的未来

    摘要 本文将详细介绍可信执行环境 TEE 的概念 原理和功能 我们将讨论TEE的应用场景 以及如何使用TEE来保护敏感数据和代码的安全 此外 我们还将探讨TEE的挑战和未来发展 1 引言 随着计算设备的普及和云计算技术的快速发展 如何保护数
  • python b 'string'

    str literals a sequence of Unicode characters UTF 16 or UTF 32 depending on how Python was compiled bytes b literals a s
  • 软件测试之自动化测试

    目录 1 什么是自动化测试 2 selenium java环境搭建 3 熟悉selenium的API 定位元素 添加等待 打印信息 浏览器的相关操作 键盘组合键用法 鼠标事件 特殊场景 定位一组元素 多层框架定位 下拉框处理 弹窗处理 上传
  • MATLAB算法实战应用案例精讲-【深度学习】归一化

    目录 归一化基础知识点 1 什么是归一化 2 为什么要归一化
  • Linux中部署软件时提示空间不足的应急方案

    1 df h 查看剩余空间 2 我们看到 home 下面的空间有很多 注 如果 home 下的控件时充足 且可以分配出去多余的空间 可以继续往下看 如自己 不够用或不够往外分配 可以外接新硬盘 3 在根号下执行 cp r home home
  • 【深度学习标注数据处理】imgaug Augment Polygons 对标注图片和 polygon 的数据增强

    对于本地化进行图像的增强 大家都是非常好操作的 但是 对于标注信息一起增强 还是稍微有一些难度的 麻烦很多 我是遇到一个数据集非常少的任务 只有40张图 就直接标记了去训练 发现几乎不拟合 当然这里使用的是yolo v8 而不是UNet 于
  • Gson的使用

    1 添加Gson 库 右键app open module settings dependncies com goolel code gson gson 2 2 4 2 对象转Json 保存至文件 使用Misc中的方法 Misc gson s
  • 3.2 图像分类

    文章目录 LeNet 小图像 LeNet在手写数字识别上的应用 LeNet在眼疾识别数据集iChallenge PM上的应用 数据集准备 查看数据集图片 定义数据读取器 启动训练 AlexNet 大图像 VGG 深度 GoogLeNet 深
  • 基于变分模态分解和麻雀算法优化长短期记忆网络的多维时间序列预测,VMD-SSA-LSTM多维时间序列预测。MATLAB代码(含LSTM、VMD-LSTM、VMD-SSA-LSTM三个模型的对比)

    clc clear all close all VMD SSA LSTM多维时间序列预测 tic load data mat load vmd data mat load LSTM mat disp disp VMD SSA LSTM预测
  • sqli-labs/Less-18

    这一关和前面的所有关卡都不一样 我们试一试先成功登录进去看看 结果除了iD地址之外还有一个信息回显了 那就是user agent所以我们抓包试一试 抓包后再user agent注入试试看 我尝试了许多注入方法 发现大部分方法都不能看出我注入
  • jdk、jre环境变量配置

    1 jdk和jre的区别 jdk Java 开发工具包 jre Java 的运行环境 只需这么记就可以了 想深入了解得自行查询相关资料 2 jdk是包含jre的 所以只需下载jdk 官方网址 https www oracle com cn
  • QT样式翻译

    Qt4 7文档翻译 Qt样式单参考 Qt Style Sheets Reference 转载于 http 2845385 blog 51cto com 2835385 1080560 0 tsina 1 14777 397232819ff9
  • 深入理解warp shuffle

    warp shuffle 相关函数学习 shfl up sync 0xffffffff lane val i 是CUDA函数之一 用于在线程束内的线程之间交换数据 其中 0xffffffff是掩码参数 指示线程束内所有线程都参与数据交换 一