[CUDA 学习笔记] half 类型的 atomicAdd 操作

half 类型的 atomicAdd 操作

  • 注: 本文主要是对文章 【BBuf的CUDA笔记】四,介绍三个高效实用的CUDA算法实现(OneFlow ElementWise模板,FastAtomicAdd模板,OneFlow UpsampleNearest2d模板) - 知乎 “0x2 FastAtomicAdd” 部分的学习整理, 参考代码 how-to-optim-algorithm-in-cuda/FastAtomicAdd

本文主要包括三个 half 数据类型的原子加操作 atomicAdd 的实现, 理论上可以拓展到 half 类型的其他原子操作, 如 atomicSub 等.

Kernel 0: CUDA atomicAdd() for half

算力 7.0 及以上的设备, CUDA 库中提供了用于 half 类型的 atomicAdd() 函数, 可以直接使用, 但性能较差.

__half atomicAdd(__half *address, __half val);

Kernel 1: pack half as half2

template<typename T, size_t pack_size>
struct alignas(sizeof(T) * pack_size) Pack {
  T elem[pack_size];
};

template<typename T, int32_t pack_size>
__device__ __inline__ void AtomicAdd(Pack<T, pack_size>* address,
                                     T val) {
  #pragma unroll
  for (int i = 0; i < pack_size; ++i) {
    atomicAdd(reinterpret_cast<T*>(address) + i, static_cast<T>(val));
  }
}

template<>
__device__ __inline__ void AtomicAdd<half, 2>(Pack<half, 2>* address, half val) {
  half2 h2_val;
  h2_val.x = static_cast<half>(val);
  h2_val.y = static_cast<half>(val);
  atomicAdd(reinterpret_cast<half2*>(address), h2_val);
}

kernel 1 的实现策略是通过 Pack<half, 2> 结构合并访问 2 个 half 元素, 从而使用 CUDA 库中 half2atomicAdd() 函数.
但在 AtomicAdd() 函数中, 相当于对 address 地址处的两个 half 元素都进行原子加的操作, 即会影响相邻元素的值, 且至少要分配 2 个 half 的大小.

Kernel 2: Pytorch fastSpecializedAtomicAdd()

Pytorch 中针对 half 数据类型提供了 fastSpecializedAtomicAdd() 的实现.

// FastAdd is referenced from
// https://github.com/pytorch/pytorch/blob/396c3b1d88d7624938a2bb0b287f2a19f1e89bb4/aten/src/ATen/native/cuda/KernelUtils.cuh#L29
template<typename T, typename std::enable_if<std::is_same<half, T>::value>::type* = nullptr>
__device__ __forceinline__ void FastSpecializedAtomicAdd(T* base, size_t offset,
                                                         const size_t length, T value) {
#if ((defined(CUDA_VERSION) && (CUDA_VERSION < 10000)) \
     || (defined(__CUDA_ARCH__) && (__CUDA_ARCH__ < 700)))
  atomicAdd(reinterpret_cast<half*>(base) + offset, static_cast<half>(value));
#else
  // Accounts for the chance base falls on an odd 16 bit alignment (ie, not 32 bit aligned)
  __half* target_addr = reinterpret_cast<__half*>(base + offset);
  // target_addr是否满足half2的内存对齐
  bool low_byte = (reinterpret_cast<std::uintptr_t>(target_addr) % sizeof(__half2) == 0);

  if (low_byte && offset < (length - 1)) {    // 内存对齐且非尾元素
    __half2 value2;
    value2.x = value;
    value2.y = __float2half_rz(0);
    atomicAdd(reinterpret_cast<__half2*>(target_addr), value2);

  } else if (!low_byte && offset > 0) {    // 内存不对齐且非首元素
    __half2 value2;
    value2.x = __float2half_rz(0);
    value2.y = value;
    atomicAdd(reinterpret_cast<__half2*>(target_addr - 1), value2);

  } else {    // 首元素不对齐 或 尾元素对齐
    atomicAdd(reinterpret_cast<__half*>(base) + offset, static_cast<__half>(value));
  }
#endif
}

template<typename T, typename std::enable_if<!std::is_same<half, T>::value>::type* = nullptr>
__device__ __forceinline__ void FastSpecializedAtomicAdd(T* base, size_t offset,
                                                         const size_t length, T value) {
  atomicAdd(base + offset, value);
}

template<class T>
__device__ __forceinline__ void FastAdd(T* base, size_t offset, const size_t length, T value) {
  FastSpecializedAtomicAdd(base, offset, length, value);
}

FastSpecializedAtomicAdd() 函数的参数: base 表示写入的起始地址, offset 为实际写入位置距起始位置 base 的偏移, lengthbase 数组长度, value 为原子增加的值.
实现的核心也是使用 CUDA 库中 half2atomicAdd() 函数, 与 kernel 1 不同的有两点:

  1. kernel 2 使用 0 填充half2 中的另一个 half 元素, 这样不会影响相邻元素的值.
  2. kernel 2 根据当前位置 offsetlength 的大小关系以及 half2 的内存对齐条件, 选择 base+offsetbase+offset+1(内存对齐且非尾元素) 或是 base+offset-1(内存不对齐且非首元素) 的元素合并为 half2 元素. 在极端情况下(首元素不对齐或尾元素对齐), 仍会退化为 half 类型的 atomicAdd() 函数. 因此, 该函数优化是针对一个 half 元素的数组的, 这也是函数名中带有 “Specialized” 的原因.

注: 参考代码 fast_atomic_add_half.cu 中, 笔者认为存在一些错误, 包括 main() 函数中 output_device 需要至少分配 2 个 half 元素大小, 即 sizeof(half)*2; 同时 dot() 函数调用 FastAdd() 时第三个参数应为 output_device 的大小 2 而非 N. 选择 2 的原因正是为了让 FastSpecializedAtomicAdd() 函数进入 half2 类型的 atomicAdd() 的分支, 而 1 个的话会退化为 half 类型的 atomicAdd().

性能笔记与总结

在 V100 上笔者测试 3 种实现性能如下:

kernel性能(ms)
kernel 0: half atomicAdd()182.45
kernel 1: pack half as half282.37
kernel 2: FastSpecializedAtomicAdd()82.36

kernel 0: half atomicAdd() :

  • 优点: 可以直接使用.
  • 缺点: 性能很差, 不如对 2 个 halfhaf2 类型的 atomicAdd().

kernel 1: pack half as half2:

  • 优点: 性能较高
  • 缺点: 必须两个 half 一起处理, 从而需要满足 half2 的内存对齐, 也会修改相邻的 half 元素

kernel 2: FastSpecializedAtomicAdd()

  • 优点: 性能较高, 不影响相邻 half 元素的值
  • 缺点: 适合 half 数组的情况, 极端情况下仍会退化为 atomicAdd(); 多个线程写入偏移不同时, 可能会造成 warp divergence.

额外一提, 在参考代码中, N 的值被设置为 32*1024*1024, 在测试过程中发现代码最后的计算结果并不正确, 笔者考虑应该是 half 类型精度导致的, 改为 double 便可得到正确结果, 或者 N 设置为 2048(及以下) 也能获得近似的正确结果. 不过 kernel 1 得到的结果好像比 kernel 0 和 2 更精确, 比如 N 设置为 4096 时, 其还能得到近似正确的结果. 笔者对计算精度不太了解, 此处仍存有疑问.

参考资料

  • 【BBuf的CUDA笔记】四,介绍三个高效实用的CUDA算法实现(OneFlow ElementWise模板,FastAtomicAdd模板,OneFlow UpsampleNearest2d模板) - 知乎

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Web漏洞-SQL注入之二次、加密、DNS加密注入

实例1&#xff1a;sqli-labs21 输入admin&#xff0c;admin 测试&#xff1a; 可以看到注入点在cookie处&#xff0c;发送到decoder&#xff08;解密&#xff09; 所以如果要注入&#xff0c;需要将注入语句加密 Eg&#xff1a;admin’ and 11加密后&#xff1a;YWRtaW4ZIGFu…

重学SpringBoot3-Profiles介绍

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-Profiles介绍 Profiles简介如何在Spring Boot中使用Profiles定义Profiles激活ProfilesIDEA设置active profile使用Profile-specific配置文件 条件化Bean…

[深度学习]yolov8+pyqt5搭建精美界面GUI设计源码实现二

【简单介绍】 基于目标检测算法YOLOv8和灵活的PyQt5界面开发框架&#xff0c;我们精心打造了一款集直观性、易用性和功能性于一体的目标检测GUI界面。通过深度整合YOLOv8在目标识别上的卓越能力与PyQt5的精致界面设计&#xff0c;我们成功研发出一款既高效又稳定的软件GUI。 …

中等职业学校大数据课程建设方案

大数据产业是以数据及数据所蕴含的信息价值为核心生产要素&#xff0c;通过数据技术、数据产品、数据服务等形式&#xff0c;使数据与信息价值在各行业经济活动中得到充分释放的赋能型产业。 大数据产业定义一般分为核心业态、关联业态、衍生业态三大业态。 一、专…

SaaS模式java智慧工地源码 有演示 AI视频智能分析解决工地安监需求

SaaS模式java智慧工地源码 AI视频智能分析解决工地安监需求 有演示 智慧工地系统充分利用计算机技术、互联网、物联网、云计算、大数据等新一代信息技术&#xff0c;以PC端&#xff0c;移动端&#xff0c;平板端三位一体的管控方式为企业现场工程管理提供了先进的技术手段。让劳…

【python从入门到精通】--第一战:安装python

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;python从入门到精通&#xff0c;魔法指针&#xff0c;进阶C&#xff0c;C语言&#xff0c;C语言题集&#xff0c;C语言实现游戏&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文…

HDFS集群环境配置

HDFS集群环境配置 环境如下三台服务器&#xff1a; 192.168.32.101 node1192.168.32.102 node2192.168.32.103 node3 一、Hadoop安装包下载​​​​​​​ 点此官网下载​​​​​​​ 二、Hadoop HDFS的角色包含&#xff1a; NameNode&#xff0c;主节点管理者DataNode&am…

大博主都不告诉你的视频号下载工具!提取视频小程序

视频下载plus一款专业的视频号视频提取工具分享平台&#xff0c;免费提供视频号视频使用教程&#xff0c;勿用于商业价值&#xff0c;分享视频下载助手以及提取视频小程序&#xff0c;仅供学习和交流。 视频下载工具 1:视频下载工具&#xff1a;常见的有视频下载软件&#xf…

计算机三级网络技术 选择+大题234笔记

上周停去准备计算机三级的考试啦&#xff0c;在考场上看到题目就知道这次稳了&#xff01;只有一周的时间&#xff0c;背熟笔记&#xff0c;也能稳稳考过计算机三级网络技术&#xff01;

【算法分析与设计】链表排序

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;算法分析与设计 ⛺️稳中求进&#xff0c;晒太阳 题目 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 示例 1&#xff1a; 输入&#xff1a;head […

计算机软考初级含金量高吗?

并不是说软考初级就没有含金量&#xff0c;对于想评初级职称的考生来说还是很有用处的。根据 国人部 发[2003]39号&#xff1a;通过考试获得证书的人员&#xff0c;表明其已具备从事相应专业岗位工作的水平和能力&#xff0c;用人单位可根据工作需要从获得证书的人员中择优聘任…

Wireshark使用实训---分析IP包

1.Wireshark简介和作用 Wireshark是一个开源的网络分析工具&#xff0c;用于捕捉和分析网络数据包。它可以帮助网络管理员和安全专家监控和解决网络问题&#xff0c;同时也可以用于学习和教学网络通信原理。 Wireshark可以在网络中捕获和分析传输的数据包&#xff0c;包括协议…

OceanBase4.2.2.1 单机集群在ArmX86安装(自测记录)

OceanBase OceanBase就不必多加介绍了&#xff0c;本次主要是分享对于它的安装使用&#xff0c;先说说背景&#xff0c;首先接触是因为信创国产化的要求&#xff0c;为满足支持国产化&#xff0c;安装了Arm架构下版本4.0.0&#xff0c;满足支持通过。后来项目实际使用&#xff…

Oracle数据库入门第二课(查询)

前面二白详细讲了一下如何下载安装Oracle以及插件&#xff0c;下面咱们正式学习一下Oracle数据库的查询语言。 DQL:数据库查询语言 一、简单查询 关键字:oracle数据库定义好的有特殊含义的字符 我们的sql语句就是由多种关键字组合而成 语法: select 要查询的内容 from 数…

操作系统入门框架

博主b站入口&#xff1a;Uncertanity的个人空间 参考资料 王道计算机网络课程 电子科技大学操作系统课件

玩具蛇(蓝桥杯)

文章目录 玩具蛇题目描述答案&#xff1a;552dfs 玩具蛇 题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 小蓝有一条玩具蛇&#xff0c;一共有 16 节&#xff0c;上面标着数字 1 至 16。每一节都是一个正方形的形…

学习人工智能:Attention Is All You Need-1-介绍;Transformer模型架构;编码器,解码器

Transformer模型是目前最成功的chatGPT&#xff0c;Sora&#xff0c;文心一言&#xff0c;LLama&#xff0c;Grok的基础模型。 《Attention Is All You Need》是一篇由Google DeepMind团队在2017年发表的论文&#xff0c;该论文提出了一种新的神经网络模型&#xff0c;即Trans…

QT 信号(Signal)与槽(Slot)机制

一、信号&#xff08;signal&#xff09;与槽&#xff08;slot&#xff09; 在QT中&#xff0c;信号&#xff08;signal&#xff09;与槽&#xff08;slot&#xff09;机制是一种用于对象间通信的重要机制。它允许一个对象发出信号&#xff0c;而其他对象可以通过连接到该信号…

电容笔品牌排行榜:2024五款便宜好用的电容笔极力推荐!

iPad作为我们最常使用的平板&#xff0c;我们想体验iPad的高效使用&#xff0c;丝滑体验&#xff0c;电容笔已经成为许多人的必备工具之一。Apple Pencil适用于专业绘图&#xff0c;一千的售价着实太高&#xff0c;如果普通学生党&#xff0c;用户使用选一款好的电容笔平替&…

多进程编程及相关函数

文章目录 查看系统中的进程进程标识进程创建进程终止僵尸进程守护进程和孤儿进程wait函数exec函数system函数 程序是存放在磁盘文件中的可执行文件。程序的执行实例被称为进程&#xff0c;进程具有独立的权限与职责。 每个进程运行在其各自的虚拟地址空间中&#xff0c;进程之间…