本文使用的是CUDA.jl
一、数据在内存和显存之间移动
如下代码所示,a、b是在内存中的两个vec
N = 2^10
a = fill(1.0f0,N)
b = fill(2.0f0,N)
如何把a、b加载到GPU内存中去?用CuArray()
a_gpu = CuArray(a)
b_gpu = CuArray(b)
如何把GPU中的array数据拷贝会内存中来?用Array()
a_cpu = Array(a_gpu)
b_cpu = Array(b_gpu)
二、案例练习:如何用GPU来完成 【a_gpu + b_gpu】的操作
【1】直接用向量化的【+】操作符完成 :a_gpu .+ b_gpu
a_gpu .+ b_gpu
总结:表达上比较自然、和蔼可亲
【2】用【map】和【+】完成
c_gpu = CUDA.map(+,a_gpu,b_gpu)
总结:表达上比较自然、符合常见的编程习惯
【3】用【map】和匿名函数完成
c_cpu = CUDA.map((x,y)->x+y,a_gpu,b_gpu)
总结:匿名函数增加了逻辑操作的丰富程度,比如你可以使用三元操作
【4】用【map】和自定义函数完成
#自定义一个add函数
function fn_add(x,y)
#bula bula 你还可以添加一大堆逻辑
return x+y
end
c_cpu = CUDA.map(fn_add,a_gpu,b_gpu)
总结:自定义function的好处就是,里面你可以写一些更复杂的逻辑,比如下面的例子:
function fn_format(x,y)
x>y ? '大' : '小'
end
c_cpu = CUDA.map(fn_format,a_gpu,b_gpu)
@show(c_cpu)
【5】自己写cuda 的kernel函数来完成
你在写CUDA核函数的时候,你需要补充一些基础知识。
以下概念你需要弄清楚:流处理器,warps,CUDA核心。Grid,blocks,threads。
function cuda_add!(a,b,c)
#blockDim().x 【线程块】的总数量
#blockIdx().x 当前【线程块】的编号
#threadIdx().x 当前【线程】在当前【线程块】中的编号
#注意cuda核函数,return nothing
index = (blockIdx().x - 1) * blockDim().x + threadIdx().x #计算下标(index)
@inbounds c[index] = a[index] + b[index]
return nothing
end
numblocks = ceil(Int, N/256) #分成多少个线程块
@cuda threads=256 blocks=numblocks cuda_add!(a_gpu,b_gpu,res)
@show(res)
注意:
(1)kernel函数不能返回具体的值,只能返回nothing
(2)函数带一个【!】叹号,代表变量会被就地更新
(3)CUDA kernel编程请参考https://github.com/JuliaGPU/CUDA.jl
三、一些奇怪的问题
下面的代码中,fn_format1和fn_format2的功能是一样的,但为红色代码出会出现报错呢?是精度导致的计算分支的问题吗?
image.png
a_d = CUDA.rand(1024)
b_d = CUDA.rand(1024)
function fn_format1(a,b)
if a > b
a+b
elseif a <=b
a*b
end
end
function fn_format2(a,b)
a>b ? a+b : a*b
end
CUDA.map(fn_format1,a_d,b_d) #此行报错
CUDA.map(fn_format2,a_d,b_d)
把if加一个else分支后,不再出问题。
image.png
网友评论