欢迎光临
我们一直在努力

Python CUDA优化(11)

Python CUDA并行算法:并行归约从Naive到Warp Shuffle的性能进化史 (Day 11)

关键词: 并行归约, Reduction, Warp Shuffle, Shared Memory, 树形归约 专栏: 《Python CUDA并行编程实战指南》 难度: ⭐⭐⭐⭐ (高级)


目录

  • 1. 归约问题:并行计算的经典难题
  • 2. V1:Naive树形归约
  • 3. V2:Shared Memory归约优化
  • 4. V3:Warp Shuffle终极优化
  • 5. 性能演进对比
  • 6. 总结与下篇预告

1. 归约问题:并行计算的经典难题

1.1 什么是归约?

定义: 将数组中的所有元素通过某种操作合并为单个值。

# 求和归约
result = sum([1, 2, 3, 4, 5]) # 结果:15

# 最大值归约
result = max([3, 1, 4, 1, 5]) # 结果:5

# 通用归约
result = reduce(op, [a, b, c, d, e])

串行算法(CPU):

def cpu_reduce_sum(arr):
result = 0
for x in arr:
result += x # O(N)时间
return result

挑战: 如何并行化这个串行依赖的操作?


2. V1:Naive树形归约

2.1 树形归约原理

原始数据:[1, 2, 3, 4, 5, 6, 7, 8]

Step 1:两两相加
[1+2, 3+4, 5+6, 7+8] = [3, 7, 11, 15]

Step 2:继续两两相加
[3+7, 11+15] = [10, 26]

Step 3:最后一次
[10+26] = [36]

总步骤数:log₂(N) = 3


2.2 Naive GPU实现

\”\”\”
V1: Naive树形归约
\”\”\”

import numpy as np
from numba import cuda
import time

@cuda.jit
def reduce_sum_naive(arr, output):
\”\”\”
Naive归约:使用Global Memory

问题:频繁访问Global Memory
\”\”\”
idx = cuda.grid(1)
stride = 1

# 树形归约
while stride < arr.size:
if idx % (2 * stride) == 0 and idx + stride < arr.size:
arr[idx] += arr[idx + stride]

stride *= 2
# 需要全局同步(不支持!)

def reduce_sum_naive_host(arr):
\”\”\”Naive归约的Host端实现\”\”\”
d_arr = cuda.to_device(arr.copy())
output = np.zeros(1, dtype=arr.dtype)

threads = 256
blocks = (arr.size + threads 1) // threads

# 注意:这个实现有bug(无法全局同步)
# 实际需要多次Kernel调用

result = d_arr.copy_to_host()
return result[0]

问题: Grid级别无法同步,需要多次Kernel调用!


3. V2:Shared Memory归约优化

3.2 完整实现

\”\”\”
V2: 使用Shared Memory的归约
\”\”\”

@cuda.jit
def reduce_sum_shared(arr, block_sums):
\”\”\”
Shared Memory归约

策略:
1. 每个Block归约为一个值
2. 存储到block_sums
3. CPU端再归约block_sums
\”\”\”
# 分配Shared Memory
shared = cuda.shared.array(256, dtype=np.float32)

idx = cuda.grid(1)
tx = cuda.threadIdx.x

# 1. 加载数据到Shared Memory
if idx < arr.size:
shared[tx] = arr[idx]
else:
shared[tx] = 0.0

cuda.syncthreads()

# 2. Block内树形归约
stride = cuda.blockDim.x // 2
while stride > 0:
if tx < stride:
shared[tx] += shared[tx + stride]
cuda.syncthreads()
stride //= 2

# 3. Thread 0写回结果
if tx == 0:
block_sums[cuda.blockIdx.x] = shared[0]

def reduce_sum_gpu_v2(arr):
\”\”\”V2完整归约流程\”\”\”
threads = 256
blocks = (arr.size + threads 1) // threads

# 第一次归约:每个Block归约为一个值
block_sums = np.zeros(blocks, dtype=np.float32)

d_arr = cuda.to_device(arr)
d_block_sums = cuda.to_device(block_sums)

reduce_sum_shared[blocks, threads](d_arr, d_block_sums)

# 第二次归约:在CPU上归约block_sums
block_sums = d_block_sums.copy_to_host()
final_sum = np.sum(block_sums)

return final_sum


4. V3:Warp Shuffle终极优化

4.1 什么是Warp Shuffle?

传统方式: 通过Shared Memory通信 Warp Shuffle: Warp内线程直接交换寄存器数据

# Warp Shuffle指令(硬件支持)
value = cuda.shfl_down_sync(mask, value, offset)


4.2 Warp Shuffle实现

\”\”\”
V3: 使用Warp Shuffle的归约
\”\”\”

from numba.cuda import shfl_down_sync

@cuda.jit
def warp_reduce(val):
\”\”\”
Warp内归约(使用Shuffle)

参数:
val: 当前线程的值

返回:
归约结果(仅Thread 0有效)
\”\”\”
# Warp大小为32
for offset in [16, 8, 4, 2, 1]:
val += shfl_down_sync(0xffffffff, val, offset)
return val

@cuda.jit
def reduce_sum_warp_shuffle(arr, block_sums):
\”\”\”使用Warp Shuffle的完整归约\”\”\”
shared = cuda.shared.array(8, dtype=np.float32) # 只需8个元素!

idx = cuda.grid(1)
tx = cuda.threadIdx.x
lane = tx % 32 # Warp内线程ID
warp_id = tx // 32 # Warp ID

# 1. 加载数据
val = arr[idx] if idx < arr

赞(0)
未经允许不得转载:171主机测评 » Python CUDA优化(11)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址