惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog RSS Feed
Spread Privacy
Spread Privacy
T
Threatpost
C
Cisco Blogs
P
Palo Alto Networks Blog
AI
AI
Cyberwarzone
Cyberwarzone
NISL@THU
NISL@THU
P
Privacy & Cybersecurity Law Blog
G
GRAHAM CLULEY
Simon Willison's Weblog
Simon Willison's Weblog
T
Tor Project blog
Latest news
Latest news
AWS News Blog
AWS News Blog
D
Docker
S
SegmentFault 最新的问题
博客园 - 聂微东
WordPress大学
WordPress大学
Vercel News
Vercel News
S
Securelist
爱范儿
爱范儿
J
Java Code Geeks
Know Your Adversary
Know Your Adversary
S
Schneier on Security
Hugging Face - Blog
Hugging Face - Blog
F
Fortinet All Blogs
Last Week in AI
Last Week in AI
D
DataBreaches.Net
宝玉的分享
宝玉的分享
D
Darknet – Hacking Tools, Hacker News & Cyber Security
MongoDB | Blog
MongoDB | Blog
Engineering at Meta
Engineering at Meta
K
Kaspersky official blog
美团技术团队
博客园 - 叶小钗
阮一峰的网络日志
阮一峰的网络日志
量子位
博客园_首页
Attack and Defense Labs
Attack and Defense Labs
S
Secure Thoughts
Google Online Security Blog
Google Online Security Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
腾讯CDC
T
Threat Research - Cisco Blogs
雷峰网
雷峰网
有赞技术团队
有赞技术团队
www.infosecurity-magazine.com
www.infosecurity-magazine.com
P
Privacy International News Feed
S
Security Affairs

博客园_首页

Plist 二进制格式 Milvus 和 PGVector,哪个更好? OpenClaw 已过时?在 VS Code 中运行 Hermes Agent! 第30篇文章:一个大三计科生的自白 Manim如何在数学公式中完美显示中文? Docker 部署 RocketMQ 5 并发编程核心概念辨析 C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生 CLI 是什么?为什么大厂突然集体卷命令行? 【从0到1构建一个ClaudeAgent】协作-自主Agent UIImageView 设置图片不生效的原因排查 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差 痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法 AI Chat 封装, SemanticKerne.AiProvider.Unified 已发布 Windows下右键编辑js文件无法打开记事本——在注册表中使用环境变量 在后台服务中使用 Scoped 服务,为什么总是报错? H200 安装驱动并使用sglang启动模型 wireshark 抓包Trap上报告警内容 我用 AI 辅助开发了一系列小工具(2):图片压缩工具 [A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型 Oracle数据库SCN推进技术详解与实践指南 玩转控件:封装个带图片的Label控件 Claude Code 4.7 真正该升级的不是模型,而是你的工作流 前端小白一句话,AI 帮我做了个颜值拉满的桌面媒体播放器。当代码不再是门槛,一句话编程就是现实。 5. WorkBuddy: 小龙虾的灵魂三件套,让你的小龙虾不只是工具 SQLite 分片方案实战:三种分片策略的深度对比 告别简陋 UI!一款基于 Fluent Design 和基于 WinUI 的开源免费、现代化的 Avalonia UI 控件库 关于二进制排列组合枚举的总结 AI开发-python-LangGraph框架(3-27-LangGraph从零实现大模型智能决策工作流) ElasticSearch主分片和副本分片概念详解 【002】HTTPS 粗解:证书、TLS 握手与对后端配置的影响 Hermes Agent 一周暴涨五万 Star,但我劝你别急着追 明明连接的是Redis的DB0,为什么能查到DB3的数据? 【从0到1构建一个ClaudeAgent】协作-Agent团队 熟悉电子元器件之后,电子小白下一步该怎么走? MAF快速入门(23)通过C#类定义Skills .NET 高级开发 | 手写一个对象映射框架 FastAPI数据库ORM怎么选?我肝了三个Demo后,终于不再纠结了 mysqldump 参数拾遗:在遗忘与铭记之间 C# .NET 周刊|2026年3月5期 Claude code入门 - 陈彦斌 一文学习入门 ThingsBoard 开源物联网平台 GitHub 热门项目 | 2026年04月16日 如何为GIT设置全局勾子,为每次提交追加信息 Number.isFinite和isFinite与isNaN()和Number.isNaN的区别 PortSwigger SQL注入LAB2 推荐一个测试人必备的Skills,从功能到性能全搞定(附详细实操和安装下载方式) 筑基期:掌握Odoo基础核心知识点02(Odoo XML 开发方式详解) GLM模型这么火,咱们用vllm也咧一个呗! 深入理解 AbortController:从底层原理到跨语言设计哲学 字符串学习笔记 多租户系统框架的基础模块设计和分析设计 Apache SeaTunnel Zeta 为什么能做到“又快又稳”? AI开发-python-LangGraph框架(3-26-LangGraph基本概念及第一个简单样例) Vue 3 组件通信,别只会用 Props 和 Emits 了,这几个狠活儿你得看看 ElasticSearch7.X版本配置密码 用Manim实现动态交点计算--从一个动点问题说起 团结引擎+Addressable+Instant Game打包抖音小游戏 function call 实战:让 LLM 自动判断 pod 异常、调用日志工具并完成故障分析 bubseek —— 让 Agent 的足迹,变成团队的洞察 通过 C# 读取并导出 PDF 书签 如何用 GitHub Actions 实现 Steam 自动化发布 【从0到1构建一个ClaudeAgent】并发-后台任务 .NET 高级开发 | 定制 ASP.NET Core 框架 电子小白:什么是运算放大器(运放) zero2Agent:面向大厂面试的 Agent 工程教程,从概念到生产的完整学习路线 堆上的ORW HC32F460 USB CDC通信异常:非对齐访问异常排查 20260413-Hyperbridge 攻击事件:发生在默克尔山上的验证绕过 那些喊着AI 要淘汰你的人,正在靠你的焦虑赚大钱! 深度学习进阶(八)Swin Transformer 最小二乘问题详解19:带先验约束的增量式SFM优化与实现 SnapTranslate 3.0 正式发布:全局划词翻译 + 完整英语学习闭环,一站式搞定查词、记词、复习 工作的意义、工作的困难认知再思考 .NET + AI 进阶实战:基于类的技能开发 - 打造可治理的 Agent 能力模块 【从0到1构建一个ClaudeAgent】规划与协调-技能 上周热点回顾(4.6-4.12) 电子小白的工具三件套:面包板、杜邦线、万能板 单表五亿数据的查询优化 | Mysql、StarRocks 2. WorkBuddy:从“我是谁”到“帮我干活” C# 如何减少代码运行时间:7 个实战技巧 基于HelixToolkit.SharpDX 渲染3D模型 - 笺上知微 从零开始的双臂具身VLA起源及现阶段发展综述 - SkyXZ 记对 xonsh shell 的使用, 脚本编写, 迁移及调优 - pluvium27 受够了Vibe Coding的失控?换个起点,让AI事半功倍 从开始配置漏洞环境到漏洞复现流程 - 難しい 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 - 虚无境 Any metadata 的内存布局 C# .NET 周刊|2026年3月2期 - InCerry 我帮你测过了,测试圈排名第二的 Skill 依然很牛逼 Skill Discovery | 无监督技能发现的经典工作总结 - MoonOut 上下文工程是什么?过时了么?一文讲明白! - 一枫说码 开了 TUN 模式还是直连?90% 的人都踩过这个坑 AScript扩展多种脚本语言 - rockey627 AI 学习笔记:Agent 的记忆机制 你能被装进一个文件里吗?——7 万人把同事"蒸馏"成了 AI - 我没有三颗心脏 Claude Code 通关手册(七):给 AI 装上技能包——Skills 完全指南 - 暮色之狐 在浏览器中快速编辑代码:VSCode Web 集成实践 - Newbe36524 蒸馏自己 skill?基于 Deepseek 的蒸馏器,丐版蒸馏方式,简单便捷 - To_Carpe_Diem Spring AI Aliababa和AgentScope,哪个更好? - 苏三说技术
CUDA软件实现跨线程块同步
RainbowC0 · 2026-06-19 · via 博客园_首页

笔者的毕设项目与 CUDA 相关,最近需要实现设备端跨线程块同步操作。查阅了相关 API 后发现有一个集群(Cluster)同步操作可用,但是集群需要计算能力 9.0+,而服务器配备 RTX 4090(计算能力 8.9),一些云算力提供 V100(计算能力 7.0)、T4(计算能力 7.5)均不达要求。最终参考了协作组的网格同步函数实现了兼容低计算能力的自定义跨线程块同步。

CUDA 线程同步

传统 CUDA 编程模型是面向线程的单指令多线程(Single-Instruction Multi-Thread, SIMT)模型,线程结构从高到低分为网格(grid)、线程块(block)、线程束(warp)、线程(thread)。

启动一个核函数(kernel)时,会把它分配到一个流多处理器(Streaming Multiprocessor, SM)上的多个流处理器(Streaming Processor, SP)上。受 SM 的寄存器等资源限制,GPU 会以 32 个线程组成一个线程束(warp)进行调度。线程束是调度和运行的基本单位。Volta 架构(计算能力 7.0)之前,线程束内的所有线程共享程序计数器(PC),并行执行相同的指令;从 Volta 架构开始,每个束内线程拥有独立的 PC,实现了更灵活的 SIMT 模型。

当一个并行任务存在多个步骤及数据依赖时,需要等待前一步所有线程执行完毕后才能执行后一步任务,这需要线程同步。CUDA 支持三种级别的线程同步:

  • 网格同步:当一个网格的所有线程均完成一个步骤后进行同步。主机端可以将任务按照步骤拆分为多个 kernel 依次执行实现网格同步,设备端则需要借助协作线程组执行grid.sync()实现。
  • 线程块同步:当一个线程块内的所有线程均完成一个步骤后进行同步。设备端通过__syncthreads()原语或者__barrier_sync()实现。
  • 线程束同步:当一个线程束内的所有线程均完成一个步骤后进行同步。Volta 架构之前线程束始终同步执行,之后需要通过设备端原语__sync_warp()实现。

当并行处理批量数据时,如果一条数据分配到一个线程块,那么内部使用__syncthreads()即可。然而,面对小批量数据时,这种分配方式导致 SM 利用率低下,通常需要将单个数据分配到多个线程块以充分利用 SM 资源。

协作组同步

CUDA 9 引入协作组(Cooperative Groups)用于组织通信线程组。协作组提供了更多层级的通信线程组划分与同步方法。协作组相关功能需要通过cooperative_groups.h引入,除了提供了网格、线程块、线程束级别的协作组同步功能,还支持集群、线程块分片(thread block tile)等介于不同级别之间的协作组。

使用较多的是网格同步,通过cooperative_groups::this_grid()取得当前网格协作组后,调用grid_group::sync()方法可进行网格同步。集群介于网格和线程块之间,由多个线程块组成,通过cooperative_groups::this_cluster().sync()进行集群同步。CUDA API 中网格同步使用软件实现,而集群同步使用__cluster_barrier_arrive()__cluster_barrier_wait()等原语实现,要求计算能力 9.0 及以上。为了实现低计算能力的类似集群同步功能,我们可以参考网格同步来软件实现。

grid_group::sync() 源码分析

以 CUDA 12.8 版本 API 源码为例,查看grid_group::sync()源码:

_CG_QUALIFIER void sync() const {
    if (!is_valid()) {
        _CG_ABORT();
    }
    details::grid::sync(&_data.grid.gridWs->barrier);
}

is_valid()用于验证_data.grid.gridWs是否非空,_data.grid.gridWs->barrier是当前网格的一个屏障变量。继续查看details::grid::sync()源码:

_CG_STATIC_QUALIFIER void sync(unsigned int *bar) {
    unsigned int token = details::sync_grids_arrive(bar);
    details::sync_grids_wait(token, bar);
}

可以看到同步操作分为同步到达和同步两步。先查看details::sync_grids_arrive()源码:

typedef unsigned int barrier_t;

_CG_STATIC_QUALIFIER bool is_cta_master() {
    return (threadIdx.x + threadIdx.y + threadIdx.z == 0);
}

_CG_STATIC_QUALIFIER unsigned int sync_grids_arrive(volatile barrier_t *arrived) {
    unsigned int oldArrive = 0;

    __barrier_sync(0); // 块内同步

    if (is_cta_master()) { // CTA内主线程
        unsigned int expected = gridDim.x * gridDim.y * gridDim.z; // 待同步的线程数量。每个线程块取一个主线程,即线程块的总数
        bool gpu_master = (blockIdx.x + blockIdx.y + blockIdx.z == 0); // 判断网格内主线程块
        unsigned int nb = 1; // 屏障自增量

        if (gpu_master) {
            // 主块的自增量需要单独设置,保证所有nb之和为0x80000000
            nb = 0x80000000 - (expected - 1);
        }

NV_IF_ELSE_TARGET(NV_PROVIDES_SM_70,
        // Barrier update with release; polling with acquire
        // SM 7.0 以上使用带有release语义的原子自加
        asm volatile("atom.add.release.gpu.u32 %0,[%1],%2;" : "=r"(oldArrive) : _CG_ASM_PTR_CONSTRAINT((unsigned int*)arrived), "r"(nb) : "memory");
        ,
        // Fence; barrier update; volatile polling; fence
        // 否则使用内存屏障&原子相加
        __threadfence();
        oldArrive = atomicAdd((unsigned int*)arrived, nb);
        );
    }
    // 返回为自增前的屏障值
    return oldArrive;
}

在此我们可以看到该同步屏障变量的使用方式。sync_grids_arrive()通过__barrier_sync(0)进行线程块同步,并通过is_cta_master()选择块内第一个线程来更新arrived屏障避免块内冲突;对于GPU/网格内的主线程,设置nb = 0x80000000 - (expected - 1)以保证所有屏障自增量nb之和为 0x80000000。这样一来,当屏障初始化为 0 时,执行依次网格同步后取值为 0x80000000,再次执行则恢复到 0,屏障可以循环使用。

_CG_STATIC_QUALIFIER bool bar_has_flipped(unsigned int old_arrive, unsigned int current_arrive) {
    return (((old_arrive ^ current_arrive) & 0x80000000) != 0);
}

_CG_STATIC_QUALIFIER void sync_grids_wait(unsigned int oldArrive, volatile barrier_t *arrived) {
    if (is_cta_master()) { // 仅 CTA 内主线程处理
NV_IF_ELSE_TARGET(NV_PROVIDES_SM_70,
        // 计算能力不低于 7.0 时使用带有 acquire 语义得加载指令读取屏障变量,并与屏障旧值比较符号
        unsigned int current_arrive;
        do {
            asm volatile("ld.acquire.gpu.u32 %0,[%1];" : "=r"(current_arrive) : _CG_ASM_PTR_CONSTRAINT((unsigned int *)arrived) : "memory");
        } while (!bar_has_flipped(oldArrive, current_arrive));
        ,
        // 否则直接读取比较,并用内存屏障保证内存可见性
        while (!bar_has_flipped(oldArrive, *arrived));
        __threadfence();
        );
    }

    __barrier_sync(0); // 线程块同步
}

sync_grids_wait()通过循环等待屏障变量最高位变动(0→0x80000000,0x80000000→0)实现同步。

实现自定义跨块同步

笔者遇到的场景是需要对blockIdx.y相同的多个blockIdx.x不同线程块进行同步。基于以上分析,我们可以为blockIdx.y相同的每一组线程设定一个屏障变量 barrier,然后将组内同步数量expected设为gridDim.x * gridDim.z、主块判断gpu_master设定为blockIdx.x == 0 && blockIdx.z == 0,即可达到效果。同步函数如下:

// sync_ctas.cuh
#pragma once
#define bar_has_flipped(a,b) ((((a)^(b))&0x80000000)!=0)
__device__ __inline__ void sync_ctas(unsigned *bar) {
  unsigned nb = blockIdx.x == 0 ? 0x80000000 - (gridDim.x - 1) : 1;
  if (threadIdx.x == 0) {
    unsigned oldarr, cuarr;
#if __CUDA__ARCH__ >= 700
    asm __volatile__("atom.add.release.gpu.u32 %0,[%1],%2;":"=r"(oldarr):"l"(bar),"r"(nb):"memory");
#else
    __threadfence();
    oldarr = atomicAdd(bar, nb);
#endif
    do {
#if __CUDA_ARCH__ >= 700
      asm __volatile__("ld.acquire.gpu.u32 %0,[%1];":"=r"(cuarr):"l"(bar):"memory");
#else
      cuarr = *(volatile unsigned*)bar;
#endif
    } while (!bar_has_flipped(oldarr, cuarr));
#if __CUDA_ARCH__ < 700
    __threadfence();
#endif
  }
  __barrier_sync(0);
}

测试

通过以下代码验证该同步功能。该代码实现了对一个数组的簇内分段重置,并与网格同步进行对比。

#include <cooperative_groups.h>
#include "sync_ctas.cuh"
#define CHECK(code) \
  {auto c=(code);\
  if (c!=cudaSuccess) {\
    fprintf(stderr, __FILE__":%d: %s\n", __LINE__, cudaGetErrorString(c));\
    abort();\
  }}
namespace cg = cooperative_groups;
 
template <bool grid_sync>
__device__ __inline__ void sync(unsigned *barrier) {
  if constexpr (grid_sync) {
    cg::this_grid().sync();
  } else {
    sync_ctas(barrier);
  }
}
 
template <bool grid_sync, bool verify=false>
__global__ void reset(int *arr, unsigned l, unsigned *barriers) {
  auto &cid = blockIdx.x;
  auto &id = blockIdx.y;
  auto barrier = barriers + id;
  arr += l * id;
  int val = 0;
  // 重置10次
  for (int j=0; j<10; j++) {
    // 多块协同重置arr
    for (unsigned i=cid * blockDim.x + threadIdx.x; i < l; i += blockDim.x * gridDim.x) {
      arr[i] = val;
    }
    sync<grid_sync>(barrier);
    if constexpr (verify) { // 单个线程校验同步逻辑正确
      if (threadIdx.x == 0 && cid == 0 && id == 0) {
        for (unsigned i=0; i < l; i++) {
          if (arr[i] != val) {
            printf("err %d\n", id);
            break;
          }
        }
      }
      sync<grid_sync>(barrier);
    }
    val = ~val;
  }
}
 
int main() {
  int l = 512;
  int *arr;
  unsigned *barr;
  cudaMalloc(&arr, l*32*sizeof(int));
  cudaMalloc(&barr, 32*sizeof(unsigned));
  cudaMemsetAsync(barr, 0, 32*sizeof(unsigned));
  cudaEvent_t st, mid, s2, end;
  dim3 gd(4,32,1), bd(128,1,1);
  cudaEventCreate(&st);
  cudaEventCreate(&mid);
  cudaEventCreate(&s2);
  cudaEventCreate(&end);
  // 校验效果
  void *args[] = {&arr, &l, &barr};
  cudaLaunchKernel(reset<false, true>, gd, bd, args, 0, 0);
  // 比较耗时
  cudaEventRecord(st);
  cudaLaunchKernel(reset<false>, gd, bd, args, 0, 0);
  cudaEventRecord(mid);
  cudaEventRecord(s2);
  cudaLaunchCooperativeKernel(reset<true>, gd, bd, args, 0, 0);
  cudaEventRecord(end);
  cudaDeviceSynchronize();
  float elp;
  cudaEventElapsedTime(&elp, st, mid);
  printf("ctas_sync %g ms\n", elp);
  cudaEventElapsedTime(&elp, s2, end);
  printf("grid_sync %g ms\n", elp);
  cudaFree(arr);
  cudaFree(barr);
  cudaEventDestroy(st);
  cudaEventDestroy(mid);
  cudaEventDestroy(s2);
  cudaEventDestroy(end);
  CHECK(cudaGetLastError());
  return 0;
}

在 RTX 4090(24G)显卡上测试,多次运行结果如下:

root@node12:~# ./a.out
ctas_sync 0.013312 ms
grid_sync 0.047104 ms
root@node12:~# ./a.out
ctas_sync 0.011264 ms
grid_sync 0.012288 ms
root@node12:~# ./a.out
ctas_sync 0.012288 ms
grid_sync 0.017408 ms
root@node12:~# ./a.out
ctas_sync 0.011264 ms
grid_sync 0.012288 ms
root@node12:~# ./a.out
ctas_sync 0.022528 ms
grid_sync 0.070656 ms

可以看到验证核函数没有输出错误字段,说明同步功能正常;输出中软件多块同步始终比网格同步更快(约1.1x~3.5x),证明了软件簇同步比网格同步更高效。

小结

本文探讨了 CUDA 的线程同步方式,并阅读协作组网格同步源码,在此基础上实现了软件级的多组同步,并通过测试验证了该方案的有效性。


原文链接:https://www.cnblogs.com/RainbowC0/p/20343006,未经作者许可禁止转载。