tiny-gpu

tiny-gpu

简化GPU实现深入解析并行计算原理

tiny-gpu是一个精简的GPU实现项目,旨在帮助学习者理解GPU工作原理。该项目聚焦通用GPU和机器学习加速器的核心原理,包括架构设计、SIMD并行化和内存管理。通过Verilog实现、架构文档和矩阵运算示例,tiny-gpu简化了复杂概念,使学习者能从底层理解现代硬件加速器的关键要素。

GPU架构并行化内存指令集Github开源项目

tiny-gpu

一个用Verilog实现的最小化GPU,旨在从底层学习GPU工作原理。

由<15个完全文档化的Verilog文件构建,包含完整的架构和指令集文档、可用的矩阵加法/乘法内核,以及对内核仿真和执行跟踪的全面支持。

目录

概述

如果你想学习CPU是如何从架构到控制信号全方位工作的,网上有很多资源可以帮助你。

但GPU并非如此。

由于GPU市场竞争激烈,所有现代架构的低层技术细节仍然是专有的。

虽然有很多资源可以学习GPU编程,但几乎没有资源可以学习GPU在硬件层面是如何工作的。

最好的选择是查看像MiaowVeriGPU这样的开源GPU实现,并尝试弄清楚它们是如何运作的。这是具有挑战性的,因为这些项目旨在功能完整且可用,所以它们相当复杂。

这就是我构建tiny-gpu的原因!

什么是tiny-gpu?

[!重要]

tiny-gpu是一个最小化的GPU实现,旨在从底层学习GPU的工作原理。

特别是,随着通用GPU(GPGPU)和像Google的TPU这样的机器学习加速器的趋势,tiny-gpu专注于突出所有这些架构的一般原则,而不是图形特定硬件的细节。

考虑到这个动机,我们可以通过去除构建生产级显卡所涉及的大部分复杂性来简化GPU,并专注于对所有这些现代硬件加速器至关重要的核心元素。

这个项目主要关注探索:

  1. 架构 - GPU的架构是什么样的?最重要的元素是什么?
  2. 并行化 - SIMD编程模型如何在硬件中实现?
  3. 内存 - GPU如何解决有限内存带宽的约束?

在理解了本项目中阐述的基础知识之后,你可以查看高级功能部分,以了解生产级GPU中一些最重要的优化(实现起来更具挑战性),这些优化可以提高性能。

架构

<p float="left"> <img src="https://yellow-cdn.veclightyear.com/835a84d5/c6a6aaf4-a717-43c9-b405-d347e66beb23.png" alt="GPU" width="48%"> <img src="https://yellow-cdn.veclightyear.com/835a84d5/c70daf7d-d2c9-4d1f-a8a5-737bcb5a1015.png" alt="Core" width="48%"> </p>

GPU

tiny-gpu被设计为一次执行一个内核。

为了启动一个内核,我们需要执行以下操作:

  1. 将内核代码加载到全局程序内存中
  2. 将必要的数据加载到数据内存中
  3. 在设备控制寄存器中指定要启动的线程数
  4. 通过将启动信号设置为高电平来启动内核

GPU本身由以下单元组成:

  1. 设备控制寄存器
  2. 调度器
  3. 可变数量的计算核心
  4. 数据内存和程序内存的内存控制器
  5. 缓存

设备控制寄存器

设备控制寄存器通常存储指定内核应如何在GPU上执行的元数据。

在这种情况下,设备控制寄存器只存储thread_count - 活动内核要启动的总线程数。

调度器

一旦内核被启动,调度器就是实际管理线程分配到不同计算核心的单元。

调度器将线程组织成可以在单个核心上并行执行的组,称为,并将这些块发送到可用的核心进行处理。

一旦所有块都被处理完毕,调度器就会报告内核执行完成。

内存

GPU被设计为与外部全局内存接口。在这里,为了简单起见,数据内存和程序内存被分开。

全局内存

tiny-gpu数据内存有以下规格:

  • 8位寻址能力(总共256行数据内存)
  • 8位数据(每行存储<256的值)

tiny-gpu程序内存有以下规格:

  • 8位寻址能力(256行程序内存)
  • 16位数据(每条指令为16位,由ISA指定)

内存控制器

全局内存有固定的读/写带宽,但所有核心的传入请求可能远远超过外部内存实际能够处理的请求。

内存控制器跟踪所有从计算核心发出的内存请求,根据实际外部内存带宽限制请求,并将外部内存的响应传回适当的资源。

每个内存控制器根据全局内存的带宽有固定数量的通道。

缓存(正在开发中)

多个核心经常从全局内存请求相同的数据。反复访问全局内存的成本很高,而且由于数据已经被获取过一次,将其存储在设备上的SRAM中以便在后续请求中更快地检索会更有效率。

这正是缓存的用途。从外部内存检索的数据存储在缓存中,并可以在后续请求中从那里检索,从而释放内存带宽以用于新数据。

核心

每个核心都有一定数量的计算资源,通常围绕它可以支持的特定数量的线程构建。为了最大化并行化,需要对这些资源进行最优管理以最大化资源利用率。

在这个简化的GPU中,每个核心一次处理一个,对于块中的每个线程,核心都有一个专用的ALU、LSU、PC和寄存器文件。管理这些资源上的线程指令执行是GPU中最具挑战性的问题之一。

调度器

每个核心都有一个单一的调度器来管理线程的执行。

tiny-gpu调度器在接收新块之前执行单个块的指令直至完成,并且同步且顺序地执行所有线程的指令。

在更高级的调度器中,使用流水线等技术来流式执行多个后续指令,以在前面的指令完全完成之前最大化资源利用率。此外,warp调度可以用于并行执行块内的多批线程。

调度器必须解决的主要约束是与从全局内存加载和存储数据相关的延迟。虽然大多数指令可以同步执行,但这些加载-存储操作是异步的,这意味着指令执行的其余部分必须围绕这些长等待时间构建。

取指器

异步地从程序内存中获取当前程序计数器处的指令(在执行单个块后,大多数实际上应该从缓存中获取)。

解码器

将获取的指令解码为线程执行的控制信号。

寄存器文件

每个线程都有自己专用的寄存器文件集。寄存器文件保存每个线程执行计算的数据,这使得同指令多数据(SIMD)模式成为可能。

重要的是,每个寄存器文件都包含一些只读寄存器,存储有关当前正在本地执行的块和线程的数据,使得可以根据本地线程ID执行具有不同数据的内核。

ALU

每个线程都有专用的算术逻辑单元来执行计算。处理 ADDSUBMULDIV 等算术指令。

还处理 CMP 比较指令,该指令实际上输出两个寄存器之差的结果是负数、零还是正数 - 并将结果存储在 PC 单元的 NZP 寄存器中。

LSU

每个线程都有专用的加载-存储单元来访问全局数据内存。

处理 LDRSTR 指令 - 并处理内存请求被内存控制器处理和传递的异步等待时间。

PC

每个单元都有专用的程序计数器,用于确定每个线程要执行的下一条指令。

默认情况下,PC 在每条指令后递增 1。

通过 BRnzp 指令,NZP 寄存器会检查 NZP 寄存器(由之前的 CMP 指令设置)是否匹配某个条件 - 如果匹配,它将分支到程序内存的特定行。这就是实现循环和条件语句的方式。

由于线程是并行处理的,tiny-gpu 假设所有线程在每条指令后都"收敛"到相同的程序计数器 - 这是为简单起见而做出的一个简单假设。

在真实的 GPU 中,单个线程可以分支到不同的 PC,导致分支分歧,即原本一起处理的一组线程需要分裂为单独执行。

ISA

ISA

tiny-gpu 实现了一个简单的 11 条指令 ISA,旨在支持简单的内核,用于概念验证,如矩阵加法和矩阵乘法(在本页面下方有实现)。

为此,它支持以下指令:

  • BRnzp - 分支指令,如果 NZP 寄存器匹配指令中的 nzp 条件,则跳转到程序内存的另一行。
  • CMP - 比较两个寄存器的值,并将结果存储在 NZP 寄存器中,以供后续的 BRnzp 指令使用。
  • ADDSUBMULDIV - 基本算术运算,用于支持张量数学。
  • LDR - 从全局内存加载数据。
  • STR - 将数据存储到全局内存。
  • CONST - 将常量值加载到寄存器中。
  • RET - 表示当前线程已到达执行结束。

每个寄存器由 4 位指定,这意味着总共有 16 个寄存器。前 13 个寄存器 R0 - R12 是支持读写的空闲寄存器。最后 3 个寄存器是特殊的只读寄存器,用于提供对 SIMD 至关重要的 %blockIdx%blockDim%threadIdx

执行

核心

每个核心遵循以下控制流程,通过不同阶段执行每条指令:

  1. FETCH - 从程序内存中获取当前程序计数器位置的下一条指令。
  2. DECODE - 将指令解码为控制信号。
  3. REQUEST - 如果需要,从全局内存请求数据(如果是 LDRSTR 指令)。
  4. WAIT - 如果适用,等待全局内存的数据。
  5. EXECUTE - 对数据执行任何计算。
  6. UPDATE - 更新寄存器文件和 NZP 寄存器。

为了简单易懂,控制流程以这种方式布局。

实际上,可以压缩这些步骤中的几个以优化处理时间,GPU 还可以使用流水线技术来在核心资源上流式处理和协调多条指令的执行,而无需等待前面的指令完成。

线程

Thread

每个核心内的每个线程都遵循上述执行路径,对其专用寄存器文件中的数据执行计算。

这类似于标准的 CPU 图,功能上也非常相似。主要区别在于 %blockIdx%blockDim%threadIdx 值位于每个线程的只读寄存器中,实现了 SIMD 功能。

内核

我使用我的 ISA 编写了矩阵加法和矩阵乘法内核,作为概念验证,以演示 SIMD 编程和在我的 GPU 上的执行。此存储库中的测试文件能够完全模拟这些内核在 GPU 上的执行,生成数据内存状态和完整的执行跟踪。

矩阵加法

这个矩阵加法内核通过在单独的线程中执行 8 个元素的逐元素加法来加两个 1 x 8 矩阵。

这个演示利用了 %blockIdx%blockDim%threadIdx 寄存器来展示这个 GPU 上的 SIMD 编程。它还使用了 LDRSTR 指令,这需要异步内存管理。

matadd.asm

.threads 8 .data 0 1 2 3 4 5 6 7 ; 矩阵 A (1 x 8) .data 0 1 2 3 4 5 6 7 ; 矩阵 B (1 x 8) MUL R0, %blockIdx, %blockDim ADD R0, R0, %threadIdx ; i = blockIdx * blockDim + threadIdx CONST R1, #0 ; baseA (矩阵 A 的基地址) CONST R2, #8 ; baseB (矩阵 B 的基地址) CONST R3, #16 ; baseC (矩阵 C 的基地址) ADD R4, R1, R0 ; addr(A[i]) = baseA + i LDR R4, R4 ; 从全局内存加载 A[i] ADD R5, R2, R0 ; addr(B[i]) = baseB + i LDR R5, R5 ; 从全局内存加载 B[i] ADD R6, R4, R5 ; C[i] = A[i] + B[i] ADD R7, R3, R0 ; addr(C[i]) = baseC + i STR R7, R6 ; 将 C[i] 存储到全局内存 RET ; 内核结束

矩阵乘法

矩阵乘法内核将两个 2x2 矩阵相乘。它执行相关行和列的点积的元素逐个计算,并使用 CMPBRnzp 指令来演示线程内的分支(值得注意的是,所有分支都收敛,因此这个内核在当前的 tiny-gpu 实现上工作)。

matmul.asm

.threads 4 .data 1 2 3 4 ; 矩阵 A (2 x 2) .data 1 2 3 4 ; 矩阵 B (2 x 2) MUL R0, %blockIdx, %blockDim ADD R0, R0, %threadIdx ; i = blockIdx * blockDim + threadIdx CONST R1, #1 ; 递增值 CONST R2, #2 ; N (矩阵内部维度) CONST R3, #0 ; baseA (矩阵 A 的基地址) CONST R4, #4 ; baseB (矩阵 B 的基地址) CONST R5, #8 ; baseC (矩阵 C 的基地址) DIV R6, R0, R2 ; row = i // N MUL R7, R6, R2 SUB R7, R0, R7 ; col = i % N CONST R8, #0 ; acc = 0 CONST R9, #0 ; k = 0 LOOP: MUL R10, R6, R2 ADD R10, R10, R9 ADD R10, R10, R3 ; addr(A[i]) = row * N + k + baseA LDR R10, R10 ; 从全局内存加载 A[i] MUL R11, R9, R2 ADD R11, R11, R7 ADD R11, R11, R4 ; B[i]的地址 = k * N + col + baseB LDR R11, R11 ; 从全局内存加载B[i] MUL R12, R10, R11 ADD R8, R8, R12 ; acc = acc + A[i] * B[i] ADD R9, R9, R1 ; k自增 CMP R9, R2 BRn LOOP ; 当k < N时循环 ADD R9, R5, R0 ; C[i]的地址 = baseC + i STR R9, R8 ; 将C[i]存储到全局内存 RET ; 内核结束

模拟

tiny-gpu设置为模拟执行上述两个内核。在模拟之前,您需要安装iverilogcocotb

安装好先决条件后,您可以使用make test_mataddmake test_matmul运行内核模拟。

执行模拟将在test/logs中输出一个日志文件,其中包含初始数据内存状态、内核的完整执行跟踪以及最终数据内存状态。

如果查看每个日志文件开头记录的初始数据内存状态,您应该能看到计算的两个起始矩阵,在文件末尾的最终数据内存中,您还应该能看到结果矩阵。

以下是执行跟踪的示例,显示了每个周期内每个核心中每个线程的执行情况,包括当前指令、PC、寄存器值、状态等。

执行跟踪

对于任何想要运行模拟或使用此仓库的人,如果遇到任何问题,请随时在twitter上给我发私信 - 我希望您能成功运行它!

高级功能

为了简单起见,现代GPU中实现的许多额外功能大大提高了性能和功能,但tiny-gpu省略了这些功能。在本节中,我们将讨论一些最关键的功能。

多层缓存和共享内存

在现代GPU中,使用多个不同级别的缓存来最小化需要从全局内存访问的数据量。tiny-gpu仅在请求内存的各个计算单元和存储最近缓存数据的内存控制器之间实现了一层缓存。

实现多层缓存允许将频繁访问的数据缓存在更靠近使用位置的地方(某些缓存位于各个计算核心内),最大限度地减少这些数据的加载时间。

使用不同的缓存算法来最大化缓存命中率 - 这是可以改进以优化内存访问的关键维度。

此外,GPU通常使用共享内存,使同一块内的线程可以访问单个内存空间,用于与其他线程共享结果。

内存合并

GPU使用的另一个关键内存优化是内存合并。并行运行的多个线程通常需要访问内存中的连续地址(例如,一组线程访问矩阵中的相邻元素)- 但每个内存请求都是单独提交的。

内存合并用于分析排队的内存请求,并将相邻的请求合并为单个事务,最大限度地减少用于寻址的时间,并一起处理所有请求。

流水线

在tiny-gpu的控制流程中,核心在开始执行下一条指令之前,会等待一组线程执行完一条指令。

现代GPU使用流水线同时流式执行多条顺序指令,同时确保相互依赖的指令仍按顺序执行。

这有助于最大化核心内的资源利用率,因为资源不会在等待时(例如:在异步内存请求期间)闲置。

线程束调度

用于最大化核心资源利用率的另一种策略是线程束调度。这种方法涉及将块分解为可以一起执行的单个线程批次。

通过在一个线程束等待时执行另一个线程束的指令,多个线程束可以同时在单个核心上执行。这类似于流水线,但处理的是来自不同线程的指令。

分支分歧

tiny-gpu假设单个批次中的所有线程在每条指令之后都会在相同的PC上结束,这意味着线程可以在整个生命周期内并行执行。

实际上,各个线程可能会根据其数据而彼此分歧并分支到不同的行。由于PC不同,这些线程需要分成不同的执行线路,这需要管理分歧的线程并注意线程何时再次汇合。

同步和障碍

现代GPU的另一个核心功能是能够设置障碍,使块中的线程组可以同步并等待同一块中的所有其他线程达到某个点后再继续执行。

这在线程需要相互交换共享数据的情况下很有用,因为它们可以确保数据已被完全处理。

下一步

我想在未来做出的更新以改进设计,欢迎其他人也贡献:

  • 为指令添加简单的缓存
  • 构建适配器以在Tiny Tapeout 7上使用GPU
  • 添加基本的分支分歧
  • 添加基本的内存合并
  • 添加基本的流水线
  • 优化控制流和寄存器使用以改善周期时间
  • 编写基本的图形内核或添加简单的图形硬件以演示图形功能

对于任何想要尝试或做出贡献的人,欢迎提交PR,添加您想要的任何改进😄

编辑推荐精选

音述AI

音述AI

全球首个AI音乐社区

音述AI是全球首个AI音乐社区,致力让每个人都能用音乐表达自我。音述AI提供零门槛AI创作工具,独创GETI法则帮助用户精准定义音乐风格,AI润色功能支持自动优化作品质感。音述AI支持交流讨论、二次创作与价值变现。针对中文用户的语言习惯与文化背景进行专门优化,支持国风融合、C-pop等本土音乐标签,让技术更好地承载人文表达。

lynote.ai

lynote.ai

一站式搞定所有学习需求

不再被海量信息淹没,开始真正理解知识。Lynote 可摘要 YouTube 视频、PDF、文章等内容。即时创建笔记,检测 AI 内容并下载资料,将您的学习效率提升 10 倍。

AniShort

AniShort

为AI短剧协作而生

专为AI短剧协作而生的AniShort正式发布,深度重构AI短剧全流程生产模式,整合创意策划、制作执行、实时协作、在线审片、资产复用等全链路功能,独创无限画布、双轨并行工业化工作流与Ani智能体助手,集成多款主流AI大模型,破解素材零散、版本混乱、沟通低效等行业痛点,助力3人团队效率提升800%,打造标准化、可追溯的AI短剧量产体系,是AI短剧团队协同创作、提升制作效率的核心工具。

seedancetwo2.0

seedancetwo2.0

能听懂你表达的视频模型

Seedance two是基于seedance2.0的中国大模型,支持图像、视频、音频、文本四种模态输入,表达方式更丰富,生成也更可控。

nano-banana纳米香蕉中文站

nano-banana纳米香蕉中文站

国内直接访问,限时3折

输入简单文字,生成想要的图片,纳米香蕉中文站基于 Google 模型的 AI 图片生成网站,支持文字生图、图生图。官网价格限时3折活动

扣子-AI办公

扣子-AI办公

职场AI,就用扣子

AI办公助手,复杂任务高效处理。办公效率低?扣子空间AI助手支持播客生成、PPT制作、网页开发及报告写作,覆盖科研、商业、舆情等领域的专家Agent 7x24小时响应,生活工作无缝切换,提升50%效率!

堆友

堆友

多风格AI绘画神器

堆友平台由阿里巴巴设计团队创建,作为一款AI驱动的设计工具,专为设计师提供一站式增长服务。功能覆盖海量3D素材、AI绘画、实时渲染以及专业抠图,显著提升设计品质和效率。平台不仅提供工具,还是一个促进创意交流和个人发展的空间,界面友好,适合所有级别的设计师和创意工作者。

图像生成AI工具AI反应堆AI工具箱AI绘画GOAI艺术字堆友相机AI图像热门
码上飞

码上飞

零代码AI应用开发平台

零代码AI应用开发平台,用户只需一句话简单描述需求,AI能自动生成小程序、APP或H5网页应用,无需编写代码。

Vora

Vora

免费创建高清无水印Sora视频

Vora是一个免费创建高清无水印Sora视频的AI工具

Refly.AI

Refly.AI

最适合小白的AI自动化工作流平台

无需编码,轻松生成可复用、可变现的AI自动化工作流

下拉加载更多