[{"data":1,"prerenderedAt":58},["ShallowReactive",2],{"post-e07c9171":3},{"abbrlink":4,"title":5,"date":6,"categories":7,"tags":9,"excerpt":11,"html":12,"toc":13},"e07c9171","CUDA编程: CUDA模型概述","2023-12-19",[8],"CUDA 编程",[10],"CUDA","摘要 讨论 GPU 的并行计算是如何在硬件上实现的，CUDA 中的模块理解以及 CPU和 GPU 之间的交互，指令的同步。","\u003Ch2 id=\"post-heading-摘要\">摘要\u003C\u002Fh2>\n\u003Cp>讨论 GPU 的并行计算是如何在硬件上实现的，CUDA 中的模块理解以及 CPU和 GPU 之间的交互，指令的同步。\u003C\u002Fp>\n\u003Ch2 id=\"post-heading-gpu-架构与异构并行计算\">GPU 架构与异构并行计算\u003C\u002Fh2>\n\u003Ch3 id=\"post-heading-什么是异构并行计算\">什么是异构并行计算\u003C\u002Fh3>\n\u003Cp>最初的计算机只包含中央处理器，为了处理越来越复杂的图形计算，GPU 营运而生，因其数据众多的轻量级线程，非常适合处理大规模异构并行计算。\u003C\u002Fp>\n\u003Cp>下图所示是一个典型的异构并行架构，包括一个 CPU及其内存 和一个 GPU及其内存，GPU 设备端通过 PCIe 总线与基于 CPU 主机端进行交互。一个异构并行应用包括主机代码和设备代码，分别运行在主机端和设备端。应用由 CPU 初始化，在设备端进行数据运算前，CPU 负责管理设备端的环境，代码和数据。我们称 host 为 CPU 及其内存，device 为 GPU 及其内存。\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"\u002Fimage\u002FCUDA%E7%BC%96%E7%A8%8B-CUDA%E6%A8%A1%E5%9E%8B%E6%A6%82%E8%BF%B0\u002F0.webp\" alt=\"\">\u003C\u002Fp>\n\u003Cp>CPU 计算适合处理控制密集型任务，GPU 计算适合处理包含数据并行的计算密集型任务。在 CPU 上执行串行部分或任务并行部分，在 GPU 上执行数据密集型并行部分，这种异构并行架构使得计算能力可以充分被利用。\u003C\u002Fp>\n\u003Ch3 id=\"post-heading-nvidia-gpu-显卡架构发展历程\">NVIDIA GPU 显卡架构发展历程\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>\u003Cstrong>Tesla\u003C\u002Fstrong>（特斯拉）2008年，应用于早期的 CUDA 系列显卡芯片中，并不是真正意义上的 GPU 芯片。\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Fermi\u003C\u002Fstrong>（费米）2010年，是第一个完整的 GPU 计算架构。首款可支持与共享存储结合纯 cache 层次的 GPU 架构，支持 ECC(Error Correcting Code) 的 GPU 架构。\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Kepler\u003C\u002Fstrong>（开普勒）2012年，Fermi 的优化版。\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Maxwell\u003C\u002Fstrong>（麦克斯韦）2014年，首次支持实时的动态全局光照效果，\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Pascal\u003C\u002Fstrong>（帕斯卡）2016年，GPU 将处理器和数据集成在同一个程序包内，以实现更高的计算效率。\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Volta\u003C\u002Fstrong>（伏打）2017年，首次将一个 CUDA 内核拆分为FP32 和 INT32 两部分，首次支持混合精度运算，提高了5倍于 Pascal 计算速度，还增加了专用于深度学习的 Tensor Core 张量单元。\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Turing\u003C\u002Fstrong>（图灵）2018年，增加了 RT Core 专用光线追踪处理器，将实时光线追踪运算加速至上一代架构的 25 倍，并能以高出 CPU 30 多倍的速度进行电影效果的最终帧渲染。去掉了对 FP64 计算的支持。\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Ampere\u003C\u002Fstrong>（安培）2020年，重新支持 FP64，新增异步拷贝指令能够从 global memory 中将数据直接加载到 SM shared memory，降低中间寄存器堆（RF）的需求。新增 BF16 数据类型，专为深度学习优化。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"post-heading-cuda-编程模型\">CUDA 编程模型\u003C\u002Fh2>\n\u003Cp>CUDA 是一个通用并行计算平台和编程模型，如下图所示，CUDA 平台可以通过 CUDA 加速库、编译器指令、应用程序编程接口或编程语言接口来使用。后面的章节我们会重点讲解 CUDA C 以及 PyCUDA 的编程。\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"\u002Fimage\u002FCUDA%E7%BC%96%E7%A8%8B-CUDA%E6%A8%A1%E5%9E%8B%E6%A6%82%E8%BF%B0\u002F1.webp\" alt=\"\">\u003C\u002Fp>\n\u003Ch3 id=\"post-heading-cuda-软件体系\">CUDA 软件体系\u003C\u002Fh3>\n\u003Cp>CUDA 提供了两层 API 来调用底层 GPU 硬件\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\n\u003Cp>CUDA 驱动 API (CUDA Driver API)\u003C\u002Fp>\n\u003Cp>是一种基于句柄的底层接口,大多数对象通过句柄被引用,其函数前缀均为\u003Ccode>cu\u003C\u002Fcode>，在调用 Driver API 前必须进行初始化，再创建 CUDA  上下文，该上下文关联到特定设备并成为主机线程的当前上下文，通过加载 PTX 汇编形式 或 二进制对象形式 的内核，然后启动内核计算。Driver API 可以通过直接操作硬件执行一些复杂的功能，但其编程较为复杂，难度较大。\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>CUDA 运行时 API (CUDA Runtime API)\u003C\u002Fp>\n\u003Cp>Runtime API 对 Driver API 进行了一定的封装，隐藏了部分实现细节，因此使用起来更为方便，因此我们更多使用的是 Runtime API。Runtime API 没有专门的初始化函数，它将在第一次调用运行时函数时自动完成初始化。使用时，通常需要包含头文件 \u003Ccode>cuda_runtime.h\u003C\u002Fcode>，其函数前缀均为cuda。\u003C\u002Fp>\n\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>如下图所示\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"\u002Fimage\u002FCUDA%E7%BC%96%E7%A8%8B-CUDA%E6%A8%A1%E5%9E%8B%E6%A6%82%E8%BF%B0\u002F2.webp\" alt=\"\">\u003C\u002Fp>\n\u003Cp>Runtime API 和 Driver API 之间没有明显的性能差距，这两种 API 不能混合使用，只用单独使用其一。\u003C\u002Fp>\n\u003Ch3 id=\"post-heading-cuda-函数库-cuda-libraries\">CUDA 函数库 (CUDA Libraries)\u003C\u002Fh3>\n\u003Cp>CUDA 提供了几个较为成熟的高效函数库，可以直接调用这些库函数进行计算，常见的包括\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\n\u003Cp>CUFFT：利用 CUDA 进行傅立叶变换的函数库\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>CUBLAS：利用 CUDA 进行加速的完整标准矩阵与向量的运算库\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>CUDPP：并行操作函数库\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>CUDNN：利用CUDA进行深度卷积神经网络\u003C\u002Fp>\n\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3 id=\"post-heading-cuda-应用程序-cuda-application\">CUDA 应用程序 (CUDA Application)\u003C\u002Fh3>\n\u003Cp>CUDA 程序包含在 host 上运行的主机代码和在 device 上运行的设备代码，设备代码会在编译时通过 CUDA  nvcc 编译器从主机代码中分离，再转换成 PTX(ParallelThread Execution) 汇编语言，由 GPU 并行线程执行，主机代码由 CPU 执行。如下图所示\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"\u002Fimage\u002FCUDA%E7%BC%96%E7%A8%8B-CUDA%E6%A8%A1%E5%9E%8B%E6%A6%82%E8%BF%B0\u002F3.webp\" alt=\"\">\u003C\u002Fp>\n\u003Cp>执行流程如下\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\n\u003Cp>分配 host 内存，并进行数据初始化（CPU初始化）\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>分配 device 内存，并从 host 将数据拷贝到 device 上（GPU初始化）\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>调用 CUDA 的核函数在 device 上完成指定的运算（GPU并行运算）\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>将 device上的运算结果拷贝到 host 上（将GPU结果传回CPU）\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>释放 device 和 host 上分配的内存（初始化清空）\u003C\u002Fp>\n\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3 id=\"post-heading-cuda-硬件结构\">CUDA 硬件结构\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>\n\u003Cp>SP（Streaming Processor）也称为 CUDA core，是最基本的处理单元，最后具体的指令和任务都是在 SP 上处理的。GPU 进行并行计算，也就是很多个 SP 同时做处理。\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>SM（Streaming Multiprocessor）多个 SP 加上其他资源组成一个 SM，也叫 GPU 大核，其他资源如包括warp scheduler，register，shared memory 等。SM可以看做GPU的心脏（类似 CPU 核心）。每个 SM 都拥有 register 和 shared memory，CUDA 将这些资源分配给所有驻留在 SM 中的线程，但资源非常有限，SM 结构如下图所示。\u003C\u002Fp>\n\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Cimg src=\"\u002Fimage\u002FCUDA%E7%BC%96%E7%A8%8B-CUDA%E6%A8%A1%E5%9E%8B%E6%A6%82%E8%BF%B0\u002F5.webp\" alt=\"\">\u003C\u002Fp>\n\u003Cp>每个 SM 包含的 SP 数量依据 GPU 架构而不同，如 Fermi 架构 GF100 是 32 个，GF10X 是 48 个，Kepler 架构都是 192 个，Maxwell 都是128 个。\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"\u002Fimage\u002FCUDA%E7%BC%96%E7%A8%8B-CUDA%E6%A8%A1%E5%9E%8B%E6%A6%82%E8%BF%B0\u002F6.webp\" alt=\"\">\u003C\u002Fp>\n\u003Cp>在软件逻辑上是所有 SP 是并行计算的，但是物理上并不是，比如只有 8 个 SM 却有 1024 个线程块需要调度处理，因为有些会处于挂起，就绪等其他状态，这有关 GPU 的线程调度，后续章节会展开讨论。\u003C\u002Fp>\n\u003Ch2 id=\"post-heading-三-理解-kernel-thread-block-grid-与-warp\">三 理解 kernel, thread, block , grid 与 warp\u003C\u002Fh2>\n\u003Ch3 id=\"post-heading-cuda-线程模型\">CUDA 线程模型\u003C\u002Fh3>\n\u003Cp>线程是程序执行的最基本单元，CUDA 的并行计算通过成千上万个线程的并行执行来实现。下图为 GPU 的线程结构\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"\u002Fimage\u002FCUDA%E7%BC%96%E7%A8%8B-CUDA%E6%A8%A1%E5%9E%8B%E6%A6%82%E8%BF%B0\u002F7.webp\" alt=\"\">\u003C\u002Fp>\n\u003Cp>CUDA的线程模型从小往大依次是\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\n\u003Cp>Thread，线程，并行的基本单位\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>Block，线程块，互相合作的线程组，线程块有如下几个特点：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\n\u003Cp>以1维、2维或3维组织\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>允许彼此同步\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>可以通过共享内存快速交换数据\u003C\u002Fp>\n\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>Grid，网格，由一组 Block 组成\u003C\u002Fp>\n\u003Cul>\n\u003Cli>以1维、2维组织\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cul>\n\u003Cli>共享全局内存\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3 id=\"post-heading-kernel\">kernel\u003C\u002Fh3>\n\u003Cp>kernel 是在 device 上线程中并行执行的函数，是软件概念，核函数用\u003Ccode>__global__\u003C\u002Fcode>符号声明，并用 \u003Ccode>&lt;&lt;&lt;grid, block&gt;&gt;&gt;\u003C\u002Fcode> 执行配置语法指定内核调用的 CUDA 线程数，每个 kernel 的 thread 都有一个唯一的线程 ID，可以通过内置变量在内核中访问。block 一旦被分配好 SM，该 block 就会一直驻留在该 SM 中，直到执行结束。一个 SM 可以同时拥有多个 blocks。\u003C\u002Fp>\n\u003Ch3 id=\"post-heading-warp\">warp\u003C\u002Fh3>\n\u003Cp>warp 是 SM 的基本执行单元，也称线程束，一个 warp 有 32 个并行的 thread， SM 旨在同时执行数百个 thread，为了管理如此大量的线程，采用了 SIMT （Single-Instruction, Multiple-Thread：单指令，多线程）的架构，也就是一个 warp 中的所有 thread 一次执行一条公共指令，并且每个thread会使用各自的data执行该指令。\u003C\u002Fp>\n\u003Cp>一个块中的 warp 总数计算如下\n\u003Csection>\u003Ceqn>\u003Cspan class=\"katex-display\">\u003Cspan class=\"katex\">\u003Cspan class=\"katex-mathml\">\u003Cmath xmlns=\"http:\u002F\u002Fwww.w3.org\u002F1998\u002FMath\u002FMathML\" display=\"block\">\u003Csemantics>\u003Cmrow>\u003Cmi>W\u003C\u002Fmi>\u003Cmi>a\u003C\u002Fmi>\u003Cmi>r\u003C\u002Fmi>\u003Cmi>p\u003C\u002Fmi>\u003Cmi>s\u003C\u002Fmi>\u003Cmi>P\u003C\u002Fmi>\u003Cmi>e\u003C\u002Fmi>\u003Cmi>r\u003C\u002Fmi>\u003Cmi>B\u003C\u002Fmi>\u003Cmi>l\u003C\u002Fmi>\u003Cmi>o\u003C\u002Fmi>\u003Cmi>c\u003C\u002Fmi>\u003Cmi>k\u003C\u002Fmi>\u003Cmo>=\u003C\u002Fmo>\u003Cmi>c\u003C\u002Fmi>\u003Cmi>e\u003C\u002Fmi>\u003Cmi>i\u003C\u002Fmi>\u003Cmi>l\u003C\u002Fmi>\u003Cmo stretchy=\"false\">(\u003C\u002Fmo>\u003Cmfrac>\u003Cmrow>\u003Cmi>T\u003C\u002Fmi>\u003Cmi>h\u003C\u002Fmi>\u003Cmi>r\u003C\u002Fmi>\u003Cmi>e\u003C\u002Fmi>\u003Cmi>a\u003C\u002Fmi>\u003Cmi>d\u003C\u002Fmi>\u003Cmi>s\u003C\u002Fmi>\u003Cmi>P\u003C\u002Fmi>\u003Cmi>e\u003C\u002Fmi>\u003Cmi>r\u003C\u002Fmi>\u003Cmi>B\u003C\u002Fmi>\u003Cmi>l\u003C\u002Fmi>\u003Cmi>o\u003C\u002Fmi>\u003Cmi>c\u003C\u002Fmi>\u003Cmi>k\u003C\u002Fmi>\u003C\u002Fmrow>\u003Cmrow>\u003Cmi>W\u003C\u002Fmi>\u003Cmi>a\u003C\u002Fmi>\u003Cmi>r\u003C\u002Fmi>\u003Cmi>p\u003C\u002Fmi>\u003Cmi>S\u003C\u002Fmi>\u003Cmi>i\u003C\u002Fmi>\u003Cmi>z\u003C\u002Fmi>\u003Cmi>e\u003C\u002Fmi>\u003C\u002Fmrow>\u003C\u002Fmfrac>\u003Cmo separator=\"true\">,\u003C\u002Fmo>\u003Cmn>1\u003C\u002Fmn>\u003Cmo stretchy=\"false\">)\u003C\u002Fmo>\u003C\u002Fmrow>\u003Cannotation encoding=\"application\u002Fx-tex\">\nWarpsPerBlock = ceil(\\frac{ThreadsPerBlock}{WarpSize}, 1)\n\u003C\u002Fannotation>\u003C\u002Fsemantics>\u003C\u002Fmath>\u003C\u002Fspan>\u003Cspan class=\"katex-html\" aria-hidden=\"true\">\u003Cspan class=\"base\">\u003Cspan class=\"strut\" style=\"height:0.8889em;vertical-align:-0.1944em;\">\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\" style=\"margin-right:0.1389em;\">W\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\">a\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\" style=\"margin-right:0.0278em;\">r\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\">p\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\">s\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\" style=\"margin-right:0.1389em;\">P\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\" style=\"margin-right:0.0278em;\">er\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\" style=\"margin-right:0.0502em;\">B\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\" style=\"margin-right:0.0197em;\">l\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\">oc\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\" style=\"margin-right:0.0315em;\">k\u003C\u002Fspan>\u003Cspan class=\"mspace\" style=\"margin-right:0.2778em;\">\u003C\u002Fspan>\u003Cspan class=\"mrel\">=\u003C\u002Fspan>\u003Cspan class=\"mspace\" style=\"margin-right:0.2778em;\">\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"base\">\u003Cspan class=\"strut\" style=\"height:2.2519em;vertical-align:-0.8804em;\">\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\">ce\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\">i\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\" style=\"margin-right:0.0197em;\">l\u003C\u002Fspan>\u003Cspan class=\"mopen\">(\u003C\u002Fspan>\u003Cspan class=\"mord\">\u003Cspan class=\"mopen nulldelimiter\">\u003C\u002Fspan>\u003Cspan class=\"mfrac\">\u003Cspan class=\"vlist-t vlist-t2\">\u003Cspan class=\"vlist-r\">\u003Cspan class=\"vlist\" style=\"height:1.3714em;\">\u003Cspan style=\"top:-2.314em;\">\u003Cspan class=\"pstrut\" style=\"height:3em;\">\u003C\u002Fspan>\u003Cspan class=\"mord\">\u003Cspan class=\"mord mathnormal\" style=\"margin-right:0.1389em;\">W\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\">a\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\" style=\"margin-right:0.0278em;\">r\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\" style=\"margin-right:0.0576em;\">pS\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\">i\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\" style=\"margin-right:0.044em;\">z\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\">e\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan style=\"top:-3.23em;\">\u003Cspan class=\"pstrut\" style=\"height:3em;\">\u003C\u002Fspan>\u003Cspan class=\"frac-line\" style=\"border-bottom-width:0.04em;\">\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan style=\"top:-3.677em;\">\u003Cspan class=\"pstrut\" style=\"height:3em;\">\u003C\u002Fspan>\u003Cspan class=\"mord\">\u003Cspan class=\"mord mathnormal\" style=\"margin-right:0.1389em;\">T\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\">h\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\" style=\"margin-right:0.0278em;\">r\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\">e\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\">a\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\">d\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\">s\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\" style=\"margin-right:0.1389em;\">P\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\" style=\"margin-right:0.0278em;\">er\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\" style=\"margin-right:0.0502em;\">B\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\" style=\"margin-right:0.0197em;\">l\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\">oc\u003C\u002Fspan>\u003Cspan class=\"mord mathnormal\" style=\"margin-right:0.0315em;\">k\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"vlist-s\">​\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"vlist-r\">\u003Cspan class=\"vlist\" style=\"height:0.8804em;\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"mclose nulldelimiter\">\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"mpunct\">,\u003C\u002Fspan>\u003Cspan class=\"mspace\" style=\"margin-right:0.1667em;\">\u003C\u002Fspan>\u003Cspan class=\"mord\">1\u003C\u002Fspan>\u003Cspan class=\"mclose\">)\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Feqn>\u003C\u002Fsection>\n对应下图\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"\u002Fimage\u002FCUDA%E7%BC%96%E7%A8%8B-CUDA%E6%A8%A1%E5%9E%8B%E6%A6%82%E8%BF%B0\u002F8.webp\" alt=\"\">\u003C\u002Fp>\n\u003Cp>从硬件角度来看，所有的 thread 以一维形式组织，每个 thread 都有个唯一的 ID，于是作为补全整数倍的 thread 在所在的 warp 中为 inactive 状态，会额外消耗 SM 资源，所以要设定 block 中的 thread 一般为32的倍数。\u003C\u002Fp>\n\u003Cp>下面从硬件角度和软件角度解释 CUDA 的线程模型\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>软件\u003C\u002Fth>\n\u003Cth>硬件\u003C\u002Fth>\n\u003Cth>描述\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>Thread\u003C\u002Ftd>\n\u003Ctd>SP\u003C\u002Ftd>\n\u003Ctd>每个线程由每个线程处理器（SP）执行\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Block\u003C\u002Ftd>\n\u003Ctd>SM\u003C\u002Ftd>\n\u003Ctd>线程块由多核处理器（SM）执行\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Grid\u003C\u002Ftd>\n\u003Ctd>Device\u003C\u002Ftd>\n\u003Ctd>一个 kernel 由一个 grid 来执行，一次只能在一个 GPU 上执行\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch3 id=\"post-heading-线程索引\">线程索引\u003C\u002Fh3>\n\u003Cp>确定线程的唯一索引，以 2D grid 和 2D block 的情况为例。\u003C\u002Fp>\n\u003Cp>我们要计算的数值矩阵在内存中是 row-major（行主序） 线性存储的，如下图\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"\u002Fimage\u002FCUDA%E7%BC%96%E7%A8%8B-CUDA%E6%A8%A1%E5%9E%8B%E6%A6%82%E8%BF%B0\u002F9.webp\" alt=\"\">\u003C\u002Fp>\n\u003Cp>将 thread 和 block 索引映射到矩阵坐标\u003C\u002Fp>\n\u003Cp>ix = threadIdx.x + blockIdx.x * blockDim.x\u003C\u002Fp>\n\u003Cp>iy = threadIdx.y + blockIdx.y * blockDim.y\u003C\u002Fp>\n\u003Cp>idx = iy * nx + ix\u003C\u002Fp>\n\u003Cp>下图为 block 和 thread 索引，矩阵坐标以及线性地址之间的关系\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"\u002Fimage\u002FCUDA%E7%BC%96%E7%A8%8B-CUDA%E6%A8%A1%E5%9E%8B%E6%A6%82%E8%BF%B0\u002F10.webp\" alt=\"\">\u003C\u002Fp>\n\u003Cp>在实践应用中，常常会多一维 grid， 那就是三维情况的索引，如下图所示，设  (gridDim.x,gridDim.y) = (2,3), (blockDim.x,blockDim.y) = (4,2)，我们以 thread_id(3,1) block_id(0,1) 为例\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"\u002Fimage\u002FCUDA%E7%BC%96%E7%A8%8B-CUDA%E6%A8%A1%E5%9E%8B%E6%A6%82%E8%BF%B0\u002F11.webp\" alt=\"\">\u003C\u002Fp>\n\u003Cp>可以得到\u003C\u002Fp>\n\u003Cp>ix = threadIdx.x + blockIdx.x * blockDim.x = 3 + 0 * 4 = 3\u003C\u002Fp>\n\u003Cp>iy = threadIdx.y + blockIdx.y * blockDim.y = 1 + 1 * 2 = 3\u003C\u002Fp>\n\u003Cp>coordinate(3,3)\u003C\u002Fp>\n\u003Cp>global index: idx = iy * blockDim.x * gridDim.x + ix = 3 * 4 * 2 + 3 = 27\u003C\u002Fp>\n",[14,18,21,25,28,31,34,37,40,43,46,49,52,55],{"id":15,"text":16,"level":17},"post-heading-摘要","摘要",2,{"id":19,"text":20,"level":17},"post-heading-gpu-架构与异构并行计算","GPU 架构与异构并行计算",{"id":22,"text":23,"level":24},"post-heading-什么是异构并行计算","什么是异构并行计算",3,{"id":26,"text":27,"level":24},"post-heading-nvidia-gpu-显卡架构发展历程","NVIDIA GPU 显卡架构发展历程",{"id":29,"text":30,"level":17},"post-heading-cuda-编程模型","CUDA 编程模型",{"id":32,"text":33,"level":24},"post-heading-cuda-软件体系","CUDA 软件体系",{"id":35,"text":36,"level":24},"post-heading-cuda-函数库-cuda-libraries","CUDA 函数库 (CUDA Libraries)",{"id":38,"text":39,"level":24},"post-heading-cuda-应用程序-cuda-application","CUDA 应用程序 (CUDA Application)",{"id":41,"text":42,"level":24},"post-heading-cuda-硬件结构","CUDA 硬件结构",{"id":44,"text":45,"level":17},"post-heading-三-理解-kernel-thread-block-grid-与-warp","三 理解 kernel, thread, block , grid 与 warp",{"id":47,"text":48,"level":24},"post-heading-cuda-线程模型","CUDA 线程模型",{"id":50,"text":51,"level":24},"post-heading-kernel","kernel",{"id":53,"text":54,"level":24},"post-heading-warp","warp",{"id":56,"text":57,"level":24},"post-heading-线程索引","线程索引",1792849564165]