这是 CUDA 的 线程组织和协作扩展,属于 高级并行编程模型,它在普通的 block / warp 层次之上引入了 更灵活的线程分组(thread grouping)机制

背景

在传统 CUDA 模型里:

  • Warp:32 个线程的基本执行单元,硬件级别同步。

  • Block:多个 warp 组成,block 内可以使用 __syncthreads() 同步。

  • Grid:多个 block 组成,可以通过 global memory 通信。

问题:

  • __syncthreads() 只能同步同一个 block 的线程。

  • 跨 block 的同步非常受限,导致一些算法(如大规模归约、scan、softmax)难以优化。

Cooperative Groups (CG)

CUDA 7.0+ 引入 Cooperative Groups API,提供 灵活的线程分组同步接口

分组类型描述同步方式
thread_block对应传统 blocksync()
coalesced_groupwarp 内线程分组,按物理执行顺序warp 内同步,可用 sync()
grid_group整个 grid 的所有线程(仅限支持 cooperative kernel)sync(),跨 block
tiled_partitionblock 内的任意线程划分成 tiletile 内同步

常用api

需要包含头文件 #include <cooperative_groups.h>

cooperative_groups::this_thread_block

namespace cooperative_groups {
    thread_block this_thread_block() noexcept;
}

返回类型:cooperative_groups::thread_block

它直接从当前线程上下文获取所在的 block 信息

  • 返回一个 thread_block 对象,表示当前线程所在的 block。

  • 你可以通过这个对象调用 block 内的各种方法

thread_block

  • 类型定义在 cooperative_groups 命名空间

  • 对应 CUDA 一个 block 的封装对象

  • 提供 block 内线程信息访问和同步接口

  • 面向对象的方式比直接用 threadIdx / __syncthreads() 更清晰

成员类型/返回值功能说明
dim_threadsdim3Block 内线程的维度信息,对应原生 blockDim
num_threads()intBlock 内线程总数,与 size() 相同,等于 blockDim.x * blockDim.y * blockDim.z
size()intBlock 内线程总数,等同 num_threads()
group_dim()dim3线程组(block)在每个维度上的大小,也就是 block 的维度(x,y,z)
group_index()dim3Block 的索引(x,y,z),对应原生 blockIdx
iddim3Block 的索引,等同 group_index()(内部别名)
thread_rank()intBlock 内线程的线性索引(0 ~ block 内总线程数-1),常用于一维化访问
thread_index()dim3Block 内线程在每个维度的索引(x,y,z),对应原生 threadIdx
thread_scope枚举描述线程所在的粒度级别(block/warp/tile 等),通常内部使用
_group_id内部实现用于标识该 block 在线程组层次中的 ID,一般不直接使用
成员返回值功能说明
sync()voidBlock 内所有线程同步,等效于 __syncthreads()
get_type()枚举返回线程组类型(thread_block 对应 block),通常内部使用
  • 常用成员

    • 线程编号/索引:thread_rank() / thread_index()

    • Block 大小信息:size() / num_threads() / dim_threads

    • Block 索引:group_index() / id

    • 同步:sync()

  • 内部/实验性

    • _group_idthread_scopeget_type() 不推荐直接使用,主要给 CG 内部或 cluster 扩展用

cooperative_groups::coalesced_threads

coalesced_group coalesced_threads();
  • 返回值

    • 类型:cooperative_groups::coalesced_group

    • 表示 warp 内连续执行线程的一个封装对象,提供 warp 内通信、同步等接口。

coalesced_group

coalesced_group 是一个 warp 封装类,把所有底层 warp intrinsics (__all_sync, __any_sync, __shfl_sync, __ballot_sync 等)
封成了更易用、更可泛化的 C++ 接口。

成员类型功能 / 说明
sync()voidwarp 内线程同步(本质上通常是 no-op,因为 warp 内天然同步,但为了泛型编程提供统一接口)
thread_rank()intwarp 内线程的线性编号(0~warpSize-1)
thread_index()intthread_rank(),便于统一接口
size()intwarp 内线程数量(一般 = warpSize = 32)
thread_scopeenum表示线程的作用域,warp 级别
_group_idint内部实现使用的 group ID,通常不直接访问

bool all(int predicate) const

功能:
判断 group 中所有线程的 predicate 是否都为真。
如果全部为真 → 返回 true,否则 false

bool any(int predicate) const

功能:
判断 group 内是否 有任意线程 满足条件。

unsigned ballot(bool predicate) const

功能:
收集 group 内每个线程的 predicate 状态,返回一个 bitmask。

const char* get_type() const

功能:
返回 group 的类型描述字符串(调试用途)。

示例输出:

  • "coalesced_group"

  • "thread_block_tile" 等。

unsigned match_all(T value, bool &pred) const

功能:
判断 group 内所有线程的值是否都相等。
如果都相等,则 pred = true 并返回该值所在的掩码。

unsigned match_any(T value) const

功能:
返回与当前线程拥有相同 value 的线程掩码。

int meta_group_rank() const

功能:
返回当前 group 在上一级 group 中的编号。
例如:block 内多个 warp,每个 warp 的 meta_group_rank() 表示它是第几个 warp。

返回值:

  • group 在上级 group(meta group)中的 rank。

int meta_group_size() const

功能:
返回上一级 group 中包含多少个子 group。
例如:一个 block 有 8 个 warp → 返回 8。

int num_threads() const

功能:
返回 group 内线程总数。
几乎等价于 size()

template <typename T> T shfl(T var, int srcLane) const

功能:
在 group 内线程间直接交换变量。
当前线程会获得 srcLane 指定线程的 var

template <typename T> T shfl_down(T var, unsigned delta) const

功能:
从当前线程的下方(编号更大的)线程获取数据。
通常用于规约。

template <typename T> T shfl_up(T var, unsigned delta) const

功能:
与上面的反向:从当前线程的上方(编号更小的)线程获取数据。

tiled_partition

template <unsigned int Size, typename ParentT>
thread_block_tile<Size, ParentT> tiled_partition(const ParentT& g)

把一个已有的线程组(比如 thread_blockcoalesced_group)划分成若干个 大小为 TileSize 的子 group(tile)
每个 tile 里包含的线程数固定为 TileSize

例如:

  • block 有 128 个线程;

  • 你调用 tiled_partition<32>(block);

  • 则这个 block 被划分为 4 个 tile(每个 32 个线程,对应 1 个 warp)。

每个线程在编译时只会知道自己属于哪个 tile,并能在 tile 内通信(shuffle、sync、reduce 等)。

api与coalesced_group基本类似

“群体算法”(Collective Algorithms)

这些算法本质上是“在一个线程组(group)内进行的并行操作”,
与 Thrust 的 STL 风格算法类似,但作用范围局限在 GPU 内部的一个 group 上。

#include <cooperative_groups/reduce.h>
#include <cooperative_groups/scan.h>
#include <cooperative_groups/memcpy_async.h>
分类典型函数功能
规约(Reduction)reduce()对 group 内所有线程的值进行求和、最值、乘积等聚合
前缀和(Scan)inclusive_scan() / exclusive_scan()对 group 内的值进行扫描(累加)
异步拷贝memcpy_async() / wait()在 group 内异步加载全局内存
广播 / shuffleshfl() / broadcast()在 group 内传递数据
同步group.sync()同步 group 内线程

reduce()

#include <cooperative_groups/reduce.h>
template <typename Group, typename T, typename BinaryOp>
__device__ T reduce(const Group& g, T value, BinaryOp op);

作用:
在指定线程组 g 内对每个线程的 value 进行聚合(如求和、求最大值等),返回结果(每个线程都返回同样的值)。

#include <cooperative_groups.h>
#include <cooperative_groups/reduce.h>
namespace cg = cooperative_groups;

__global__ void kernel(float* data) {
    cg::thread_block block = cg::this_thread_block();
    auto tile32 = cg::tiled_partition<32>(block);

    float x = data[block.thread_rank()];
    float sum = cg::reduce(tile32, x, cg::plus<float>());

    if (tile32.thread_rank() == 0)
        printf("Tile sum = %f\n", sum);
}

exclusive_scan() / inclusive_scan()

#include <cooperative_groups/scan.h>
template <typename Group, typename T, typename BinaryOp>
__device__ T exclusive_scan(const Group& g, T value, BinaryOp op);

template <typename Group, typename T, typename BinaryOp>
__device__ T inclusive_scan(const Group& g, T value, BinaryOp op);

作用:
对 group 内的所有线程执行前缀操作(前缀和、前缀最大值等)。

#include <cooperative_groups/scan.h>
namespace cg = cooperative_groups;

__global__ void prefix_sum(float *data) {
    cg::thread_block block = cg::this_thread_block();
    auto tile8 = cg::tiled_partition<8>(block);

    float x = data[block.thread_rank()];
    float prefix = cg::inclusive_scan(tile8, x, cg::plus<float>());

    data[block.thread_rank()] = prefix;
}

memcpy_async() / wait()

#include <cooperative_groups/memcpy_async.h>
template <typename Group>
__device__ void memcpy_async(const Group& g, void* dst, const void* src, size_t size);

template <typename Group>
__device__ void wait(const Group& g);

作用:
在 group 内发起异步数据传输(比如从 global memory 到 shared memory),
通常用于 Ampere 及更高架构的 GPU 上,用来隐藏内存访问延迟。

#include <cooperative_groups/memcpy_async.h>
namespace cg = cooperative_groups;

__global__ void async_copy_kernel(float* dst, const float* src) {
    extern __shared__ float smem[];

    cg::thread_block block = cg::this_thread_block();

    cg::memcpy_async(block, smem, src, 256 * sizeof(float)); // 异步拷贝
    cg::wait(block); // 等待拷贝完成

    // 使用 smem 中的数据
    smem[block.thread_rank()] *= 2;
}

常用的辅助函数和运算符

运算符类型作用
cg::plus<T>()二元函数对象加法
cg::maximum<T>()二元函数对象求最大值
cg::minimum<T>()二元函数对象求最小值
cg::multiplies<T>()二元函数对象乘法

这些类似于 thrust::plus / std::plus,用在 reducescan 等算法里。

规约碎碎念

到目前为止,已经见过CUB库的规约,Thrust库的规约和cooperative_groups库的规约了

CUB 库特点

  • 极致性能(由 NVIDIA 工程师手写 PTX 优化)

  • 支持泛型操作(不仅是 sum,可自定义 ReduceOp

  • 支持 warp、block、device 三层规约

  • 自动处理边界、bank conflict、同步

Thrust 库特点

  • 类似 C++ STL 算法接口

  • 代码简洁

  • 性能比手写略低(但仍然不错)

  • 适合快速原型、科研、教学

cooperative_groups 库特点

  • 可以定义 warp/block/tile***/多block group

  • 支持 同步(sync)、规约(reduce)、广播(broadcast)

  • 比 warp shuffle 更抽象、可移植

跨 block 同步

cooperative_groups 默认并不支持跨 block 的同步或规约
要想真正实现 “跨 block” 的 group(grid 级同步),需要显式启用“cooperative launch”机制

group 类型说明是否支持跨 block
thread_block当前 block 内线程组❌ 否
thread_block_tile<N>block 内的 warp/tile 分组❌ 否
grid_group整个 grid 的所有线程组✅ 是
multi_grid_group多 grid(多 kernel)级组✅ 是(rare)

跨 block 同步必须启用 cooperative launch

普通的 kernel 启动方式:

kernel<<<gridDim, blockDim>>>(...);

这种 无法保证 所有 block 同时在 GPU 上执行,因此不能安全地跨 block 同步。

要启用 跨 block 同步(即 grid_group),必须满足两个条件:

✅ 条件 1:使用 cooperative launch API

void *kernelArgs[] = { &input, &output };
cudaLaunchCooperativeKernel((void*)kernel, gridDim, blockDim, kernelArgs);
cudaError_t cudaLaunchCooperativeKernel(const void *func, dim3 gridDim, dim3 blockDim,
 void **args, size_t sharedMem, cudaStream_t stream)

✅ 条件 2:硬件支持

  • Pascal (SM 6.0) 及以上 GPU 支持 cooperative launch。

  • 驱动和设备属性必须满足:

cudaDeviceProp prop;
cudaGetDeviceProperties(&prop, device);
if (!prop.cooperativeLaunch) { /* 不支持跨block同步 */ }

示例代码

#ifndef __CUDACC__
#define __CUDACC__
#endif
#include <cuda_runtime.h>
#include <iostream>
#include <cooperative_groups.h>
#include <thrust/device_vector.h>
#include <thrust/host_vector.h>

namespace cg = cooperative_groups;

__global__ void kernel(float *data, int N) {
    cg::grid_group grid = cg::this_grid();  // 获取整个 grid 的 group

    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < N)
        data[idx] += 1.0f;

    grid.sync();  // 所有 block 同步

    if (idx < N)
        data[idx] *= 2.0f;
}

int main() {
    int N = 64;
    int blocksize = 32;
    int gridsize  = 2;

    thrust::device_vector<float> d_vec(N, 1.0f);
    thrust::host_vector<float> h_vec(N);

    float *d_ptr = thrust::raw_pointer_cast(d_vec.data());

    // ✅ 检查设备是否支持 cooperative launch
    cudaDeviceProp prop;
    cudaGetDeviceProperties(&prop, 0);
    if (!prop.cooperativeLaunch) {
        std::cerr << "Device does NOT support cooperative launch!" << std::endl;
        return 1;
    }

    // ✅ 检查 grid 是否能完全驻留
    int maxBlocksPerSM;
    cudaOccupancyMaxActiveBlocksPerMultiprocessor(
        &maxBlocksPerSM, kernel, blocksize, 0);

    int maxBlocks = maxBlocksPerSM * prop.multiProcessorCount;
    if (gridsize > maxBlocks) {
        std::cerr << "Grid too large for cooperative launch! "
                     "Max allowed: " << maxBlocks << std::endl;
        return 1;
    }

    // ✅ 参数数组
    void *args[] = { &d_ptr, &N };

    // ✅ 启动 cooperative kernel
    cudaError_t err = cudaLaunchCooperativeKernel(
        (void*)kernel, gridsize, blocksize, args, 0, nullptr);

    if (err != cudaSuccess) {
        std::cerr << "Launch failed: " << cudaGetErrorString(err) << std::endl;
        return 1;
    }

    cudaDeviceSynchronize();

    h_vec = d_vec;
    for (int i = 0; i < h_vec.size(); i++)
        std::cout << h_vec[i] << " ";
    std::cout << std::endl;
}

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐