这个系列我们来讲解CUDA,从入门到可以独立写自己的cuda代码,现在开启我们的cuda之旅吧。
CPU:少量非常强的核心,一个核心很强,擅长:
- 复杂逻辑
- 分支判断
- 操作系统
- Python 程序
- 各种串行任务
GPU:大量相对简单的计算单元,同时执行大量相似任务。
CPU有内存,RAM,GPU有显存,VRAM。
x = torch.randn(1000, 1000)//默认是CPU RAM
print(x.device)//CPU
x = torch.randn(1000, 1000, device="cuda")//GPU VRAM
print(x.device)//cuda:0 第 0 张 NVIDIA GPU
y = torch.randn(1000, 1000, device="cuda")
z = x @ y//矩阵乘法
第一个程序(建议手写)
python
import torch
print("CPU:")
x = torch.randn(3,3)
print(x)
print("device:",x.device)
print()
print("GPU:")
y = torch.randn(3,3,device="cuda")
print(y)
print("device:",y.device)
什么是CUDA kernel?
可以把它理解为让 GPU 执行的一段程序,比如我们需要Ci = Ai + Bi;有1亿个元素,GPU启动大量线程
Thread 0 → C[0] = A[0] + B[0]
Thread 1 → C[1] = A[1] + B[1]
Thread 2 → C[2] = A[2] + B[2]
Thread 3 → C[3] = A[3] + B[3]
...
Thread 99999999 → C[99999999] = ...
当然GPU不是真的同时拥有这么多的物理计算核心,所以我们需要把thread分组,若干个thread合成一个block,我们再将所有的block集合为grid,于是形成了一个层级结构
Grid
│
┌──────────┼──────────┐
↓ ↓ ↓
Block 0 Block 1 Block 2
│ │ │
Threads Threads Threads
GPU内部存在一种叫SM的计算单位,暂时可以理解为GPU里面真正负责执行CUDA Thread的计算单元。我们现在不讨论这个,就直接进入threads步骤,每个thread都有自己的ID,CUDA给我们提供threadIdx来获取thread的编号
eg:int i = threadIdx.x;//c++
Ci = Ai + Bi;//thread就知道自己负责谁了
这个i其实不会太大,因为block的存在,如果大了的话,我们会换一个block,同样的,CUDA提供blockIdx表示我是第几个block,再加上threadIdx,表示我是这个block的第几个thread,于是全局的Thread ID可以计算
c++
int i = blockIdx.x*blockDim.x+threadIdx.x;
我们来算一下,block0 thread0是多少?
假设每个block有256个thread
int i = 0 * 256+0=0;这个就是CUDA把大数组分给GPU的核心方法。
CUDA程序实际上是在协调CPU和GPU。
CPU
│
│ 启动 Kernel
↓
GPU
│
↓
CUDA Kernel
│
┌──────┼──────┐
↓ ↓ ↓
Thread Thread Thread
first CUDA
我们之前写的import torch,它使用torch库来直接调用CUDA Kernel,现在我们开始接触真正的CUDA C++。
__global__ void add(float* A, float* B, float* C)
{
int i = blockIdx.x * blockDim.x + threadIdx.x;
C[i] = A[i] + B[i];
}