CUDA1_CUDA编程基础

CUDA(一):CUDA 编程基础

文章来源:知乎专栏 - CUDA(一):CUDA 编程基础
文章ID:645330027
发布时间:编辑于 2025-07-08 14:25・上海


一、GPU 与 CUDA 架构深度解析

1.1 GPU 与 CPU 的设计哲学差异

处理器的两个核心指标是延迟(Latency)和吞吐量(Throughput)

CPU:延迟导向设计

CPU 的架构特点:

  1. 多级高速缓存结构:提升指令访问存储速度
  2. 复杂控制单元
  1. 强大的运算单元(Core):整型和浮点型复杂运算速度快

设计目标:减少单条指令的执行延迟

GPU:吞吐导向设计

/

性能对比与适用场景

性能差异

GPU 适用场景

  1. 计算密集型任务:数值计算比例远大于内存操作,内存访问延时可被计算掩盖
  2. 数据并行任务:大任务可拆解为执行相同指令的小任务,对复杂流程控制需求较低

1.2 CUDA 硬件架构层次

CUDA(Compute Unified Device Architecture)是支持 GPU 通用计算的平台和编程模型,提供 C/C++ 语言扩展和用于编程管理 GPU 的 API。

硬件层次结构(从底层到顶层)

1. SP(Stream Processor,线程处理器)

2. SM(Streaming Multiprocessor,多核处理器)

3. GPU(Device)

内存层次总结

从线程视角看内存访问权限:

1.3 CUDA 软件抽象模型

硬件与软件的对应关系:

Thread Block(线程块)特性

线程块是软件侧的基本执行单位,具有以下特点:


二、CUDA 编程核心要素

2.1 Kernel 函数与线程组织

Kernel 定义

线程索引与线程 ID 的关系

2.2 矩阵加法示例:单 Block 版本

// Kernel definition
__global__ void MatAdd(float A[N][N], float B[N][N], float C[N][N])
{
    int i = threadIdx.x;
    int j = threadIdx.y;
    C[i][j] = A[i][j] + B[i][j];
}

int main()
{
    ...
    // Kernel invocation with one block of N * N * 1 threads
    int numBlocks = 1;
    dim3 threadsPerBlock(N, N);
    MatAdd<<<numBlocks, threadsPerBlock>>>(A, B, C);
    ...
}

关键点

2.3 矩阵加法示例:多 Block 版本

// Kernel definition
__global__ void MatAdd(float A[N][N], float B[N][N], float C[N][N])
{
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    int j = blockIdx.y * blockDim.y + threadIdx.y;
    if (i < N && j < N)
        C[i][j] = A[i][j] + B[i][j];
}

int main()
{
    ...
    // Kernel invocation
    dim3 threadsPerBlock(16, 16);
    dim3 numBlocks(N / threadsPerBlock.x, N / threadsPerBlock.y);
    MatAdd<<<numBlocks, threadsPerBlock>>>(A, B, C);
    ...
}

关键改进

内置变量

2.4 线程同步机制

**__syncthreads() 函数**:

重要限制

2.5 Warp(线程束):GPU 执行的最小单元

SIMT 架构(Single-Instruction, Multiple-Thread)

核心概念

Warp 定义

实践要点

关键优化建议

Kernel 执行流程

当一个 kernel 被执行时:

  1. Grid 中的线程块被分配到 SM 上
  2. 一个线程块的 thread 只能在一个 SM 上调度
  3. 一个 SM 通常可以调度多个线程块
  4. 大量 thread 可能被分配到不同的 SM 上
  5. 每个 thread 拥有独立的程序计数器和状态寄存器
  6. 每个 thread 用自己的数据执行指令(SIMT)

硬件限制


文章概要

本文是关于CUDA(一):CUDA 编程基础的技术文章,属于高性能计算与AI基础设施系列。


核心内容

一、GPU 与 CUDA 结构

1.1 GPU 再认识

对于处理器而言,有2个指标是最主要的:延迟吞吐量
。延迟,是指从发出指令到最终返回结果中间经历的时间间隔。而吞吐量,就是单位之间内处理的指令的条数。下面将主要从这两个方面来比较GPU和CPU:

下图左是 CPU 的示意图,有以下几个特点:

  1. CPU 中包含了多级高速的缓存结构。 这样提升了指令访问存储的速度。
  2. CPU 中包含了很多控制单元。 具体有2种,一个是分支预测机制,另一个是流水线前传机制。
  3. CPU 的运算单元 (Core) 强大,整型浮点型复杂运算速度快。

基于以上三点,CPU 在设计时的导向就是减少指令的时延,被称之为延迟导向设计

下图右是 GPU 的示意图,有以下几个特点:

  1. GPU 中虽有缓存结构但是数量少。 因为要减少指令访问缓存的次数。
  2. GPU 中控制单元非常简单。 控制单元中没有分支预测机制和数据转发机制,对于复杂的指令运算就会比较慢。
  3. GPU 的运算单元 (Core) 非常多,采用长延时流水线以实现高吞吐量。 每一行的运算单元的控制器只有一个,意味着每一行的运算单元使用的指令是相同的,不同的是它们的数据内容。那么这种整齐划一的运算方式使得 GPU 对于那些控制简单但运算高效的指令的效率显著增加。

基于此,可以看到 GPU 在设计过程中以一个原则为核心:增加简单指令的吞吐,这称 GPU 为吞吐导向设计。

GPU vs CPU

由于设计原则不同,二者擅长的场景有所不同:

进一步可以具体化适合 GPU 的场景:

  1. 计算密集:数值计算的比例要远大于内存操作,因此内存访问的延时可以被计算掩盖。
  2. 数据并行:大任务可以拆解为执行相同指令的小任务,因此对复杂流程控制的需求较低。

2.2 CUDA 结构

CUDA (Compute Unified Device Architecture)是支持 GPU 通用计算的平台和编程模型,提供 C/C++ 语言扩展和
用于编程和管理 GPU的API。

从硬件的角度来讲,CUDA 内存模型的最基本的单位就是
[SP](https://zhida.zhihu.com/search?content_id=231537480&content_type=Article&match_order=1&q=SP&zd_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ6aGlkYV9zZXJ2ZXIiLCJleHAiOjE3NzgxMzY4NzAsInEiOiJTUCIsInpoaWRhX3NvdXJjZSI6ImVudGl0eSIsImNvbnRlbnRfaWQiOjIzMTUzNzQ4MCwiY29udGVudF90eXBlIjoiQXJ0aWNsZSIsIm1hdGNoX29yZGVyIjoxLCJ6ZF90b2tlbiI6bnVsbH0.jqrEBLK7YokxKnurB6qTFjSv-
wXi8ZfsI-99O_rvIhs&zhida_source=entity) (线程处理器)
。每个线程处理器 (SP) 都用自己的
registers (寄存器)local memory (局部内存)
。寄存器和局部内存只能被自己访问,不同的线程处理器之间是彼此独立的。

由多个线程处理器 (SP) 和一块共享内存所构成的就是 SM (多核处理器)
(灰色部分)。多核处理器里边的多个线程处理器是互相并行的,是不互相影响的。每个多核处理器 (SM) 内都有自己的 shared memory
(共享内存),shared memory 可以被线程块内所有线程访问。

再往上,由这个 SM (多核处理器) 和一块全局内存,就构成了 GPU。一个 GPU 的所有 SM 共有一块 global memory
(全局内存),不同线程块的线程都可使用。

上面这段话可以表述为:每个 thread 都有自己的一份 register 和 local memory 的空间。同一个 block 中的每个 thread
则有共享的一份 sha


三、实践:PyTorch 自定义 CUDA 算子

本节实现一个简单的 CUDA 算子(两个 n×n tensor 相加),并通过 PyTorch 调用。

3.1 算子设计与实现

设计规格

索引映射关键

每个线程需要从线程索引映射到全局线性内存索引:

CUDA 实现代码

__global__ void MatAdd(float* c,
                       const float* a,
                       const float* b,
                       int n)
{
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    int j = blockIdx.y * blockDim.y + threadIdx.y;
    int idx = j*n + i;
    if (i < n && j < n)
        c[idx] = a[idx] + b[idx];
}

void launch_add2(float* c,
                 const float* a,
                 const float* b,
                 int n) {
    dim3 block(16, 16);
    dim3 grid(n/block.x, n/block.y);
    
    MatAdd<<<grid, block>>>(c, a, b, n);
}

关键点

3.2 Torch C++ 封装

CUDA kernel 函数 PyTorch 不能直接调用,需要提供接口(add2_ops.cpp):

#include <torch/extension.h>
#include "add2.h"

void torch_launch_add2(torch::Tensor &c,
                       const torch::Tensor &a,
                       const torch::Tensor &b,
                       int64_t n) {
    launch_add2((float *)c.data_ptr(),
                (const float *)a.data_ptr(),
                (const float *)b.data_ptr(),
                n);
}

PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
    m.def("torch_launch_add2",
          &torch_launch_add2,
          "add2 kernel warpper");
}

TORCH_LIBRARY(add2, m) {
    m.def("torch_launch_add2", torch_launch_add2);
}

工作流程

  1. torch_launch_add2 接收 C++ 版本的 torch tensor
  2. 转换为 C++ 指针数组
  3. 调用 CUDA 函数 launch_add2 执行核函数
  4. 使用 pybind11 封装,生成 Python 可调用的 .so 库

Torch 使用 CUDA 算子的三步骤

  1. 编写 CUDA 算子和对应的调用函数
  2. 编写 torch cpp 函数建立 PyTorch 和 CUDA 之间的联系,用 pybind11 封装
  3. 用 PyTorch 的 cpp 扩展库进行编译和调用

3.3 三种编译调用方法

方法一:JIT(Just-In-Time)编译

特点:Python 代码运行时即时编译 cpp 和 cuda 文件

from torch.utils.cpp_extension import load
cuda_module = load(name="add2",
                   extra_include_paths=["include"],
                   sources=["kernel/add2_ops.cpp", "kernel/add2_kernel.cu"],
                   verbose=True)

cuda_module.torch_launch_add2(cuda_c, a, b, n)

执行命令

python run_time.py --compiler jit

性能结果

方法二:SETUP 编译

特点:通过 Setuptools 预编译

from setuptools import setup
from torch.utils.cpp_extension import BuildExtension, CUDAExtension

setup(
    name="add2",
    include_dirs=["include"],
    ext_modules=[
        CUDAExtension(
            "add2",
            ["kernel/add2_ops.cpp", "kernel/add2_kernel.cu"],
        )
    ],
    cmdclass={
        "build_ext": BuildExtension
    }
)

编译过程

python setup.py install

核心操作:

[1/2] nvcc -c add2_kernel.cu -o add2_kernel.o
[2/2] c++ -c add2.cpp -o add2.o
x86_64-linux-gnu-g++ -shared add2.o add2_kernel.o -o add2.cpython-37m-x86_64-linux-gnu.so

调用方式

import torch
import add2
add2.torch_launch_add2(c, a, b, n)

执行命令

python run_time.py --compiler setup

性能结果

方法三:CMAKE 编译

特点:使用 CMake 构建系统

cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
set(CMAKE_CUDA_COMPILER "/usr/local/cuda/bin/nvcc")
project(add2 LANGUAGES CXX CUDA)

find_package(Python REQUIRED)
find_package(CUDA REQUIRED)

execute_process(
    COMMAND
        ${Python_EXECUTABLE} -c
            "import torch.utils; print(torch.utils.cmake_prefix_path)"
    OUTPUT_STRIP_TRAILING_WHITESPACE
    OUTPUT_VARIABLE DCMAKE_PREFIX_PATH)

set(CMAKE_PREFIX_PATH "${DCMAKE_PREFIX_PATH}")

find_package(Torch REQUIRED)
find_library(TORCH_PYTHON_LIBRARY torch_python PATHS "${TORCH_INSTALL_PREFIX}/lib")

include_directories(/usr/include/python3.7)
include_directories(../include)

set(SRCS ../kernel/add2_ops.cpp ../kernel/add2_kernel.cu)
add_library(add2 SHARED ${SRCS})

target_link_libraries(add2 "${TORCH_LIBRARIES}" "${TORCH_PYTHON_LIBRARY}")

编译命令

mkdir build
cd build
cmake ..
make

注意事项

调用方式

import torch
torch.ops.load_library("build/libadd2.so")
torch.ops.add2.torch_launch_add2(c, a, b, n)

执行命令

python run_time.py --compiler cmake

性能结果


四、核心知识点总结

4.1 架构理解

  1. GPU vs CPU
  1. 硬件层次:SP → SM → GPU
  2. 软件层次:Thread → Block → Grid
  3. 内存层次:Register/Local Memory → Shared Memory → Global Memory

4.2 编程要点

  1. Kernel 函数:使用 __global__ 修饰,由多个线程并行执行
  2. 索引计算globalIdx = blockIdx * blockDim + threadIdx
  3. 线程同步__syncthreads() 仅同步块内线程
  4. Warp 优化:Block 大小设为 32 的倍数
  5. 边界检查:防止线程索引越界

4.3 PyTorch 集成

  1. 三层结构
  1. 三种编译方式

4.4 性能观察

从测试结果看,H100 相比 V100 性能提升显著(约 150 倍),体现了新一代 GPU 架构的优势。


参考资料

  1. NVIDIA CUDA C Programming Guide
  2. CUDA 编程上手指南(一):CUDA C 编程及 GPU 基本知识
  3. godweiyang/NN-CUDA-Example
  4. PyTorch 自定义 CUDA 算子教程(一)
  5. PyTorch 自定义 CUDA 算子教程(二)

渺万里层云,千山暮雪,只影向谁去? —— 元好问《摸鱼儿·雁丘词》