K8凯发(中国)天生赢家·一触即发

logo
首页 关于凯发k8 集团简介 董事长致词 发展战略 荣誉资质 企业文化 集团产业 技术创新 平台创新 国际创新 产品中心 智慧显示终端 电致变色玻璃 模拟芯片 5G非导新材料镀膜 超高清传媒 解决方案 智能教育 智能金融 智能办公 智能医疗 智能交通 新闻中心 凯发k8旗舰厅 凯发官网入口 凯发k8娱乐官网手机 K8凯发集团从零开始 凯发k8国际校地合作 凯发k8官方旗舰厅焕 凯发K8旗舰厅App 凯发k8娱乐官网手机 凯发k8国际娱乐官网 凯发k8娱乐官网ap 凯发K8官方网娱乐官 凯发k8旗舰厅 芯片 商务合作 销售代理 产品直销 加入我们 联系我们 人才理念 人才培养 凯发k8一触即发 凯发k8国际娱乐官网 凯发k8国际娱乐官网 凯发国际K8官网最后 凯发国际K8官网香港 凯发K8旗舰厅App 凯发k8娱乐-点燃球 凯发k8国际手机ap 凯发K8官方网娱乐官 凯发K8旗舰厅App 凯发在线杨紫王阳合作
凯发官网入口 首页 > 新闻中心 > 凯发官网入口

K8凯发集团从零开始设计一个GPU:附详细流程

浏览: 发布时间:2024-05-08 20:17:39

  但GPU 不一样□▲▽△…□。由于 GPU 市场竞争如此激烈•▪◇◇▼▪,所有现代架构的底层技术细节仍然是专有的○☆•。虽然有很多资源可以学习 GPU 编程▲•,但几乎没有任何资源可以学习 GPU 在硬件级别的工作原理▷☆▲……-。

  每个线程都有它自己的专用寄存器文件集□▲▼。寄存器文件保存每个线程正在执行计算的数据▷▼◇●●,从而实现同一指令多数据 (SIMD) 模式▪▷-◆▪。

  在这个简化的 GPU 中□■◆□,每个核心一次处理一个块▪■•-△★,对于块中的每个线程□▷▪-□…,核心都有一个专用的 ALU●=…、LSU◇▽■、PC 和寄存器文件△▲▼▽。管理这些资源上线程指令的执行是 GPU 中最具挑战性的问题之一△★==。

  但原作者依然继续了这个他认为比较难的任务▷=■▲□,并分享了自己DIY GPU的全流程-▼=•△。

  为了实现这一点◁■△★○,我受 LC4 ISA 的启发制作了自己的小型 11 指令 ISA◇□▲,以允许我编写一些简单的矩阵数学内核作为概念证明★★。

  内存合并用于分析排队的内存请求=▽,并将相邻请求合并到单个事务中▷◁=•▽,最大限度地减少寻址所花费的时间■△☆▲•▷,并将所有请求放在一起•◁▷▲。

  CMP- 比较两个寄存器的值并将结果存储在 NZP 寄存器中以供后续BRnzp指令使用▲▽••★。

  REQUEST- 如有必要▪■★★▽,从全局存储器请求数据(ifLDR或STR指令)○=△◁。

  具体来说▽◆○▲-,随着通用 GPU (GPGPU) 和 ML 加速器(如 Google 的 TPU)的发展趋势▽▲…▲□,tiny-gpu 专注于强调所有这些架构的一般原理△▲▼■,而不是图形特定硬件的细节◆•■。

  但是-△,尽管困难重重◆★,但这是我的许多学习真正沉入深层直觉的一步○▼◇▼•。通过直面问题◆●☆▼-◇,我对 GPU 所面临的挑战有了更本能的感觉▪○。

  这是我在 GPU 上运行矩阵加法内核的视频(视频参考原文链接)◁•,浏览 GPU 运行的执行跟踪◁▪▪■,然后检查 GPU 存储最终结果的数据存储器中的最终状态★★。

  每个内核都指定要操作的矩阵▼▼、要启动的线程数以及要在每个线程中执行的代码■•○。

  STR/LDR - 在全局数据存储器中存储/加载数据以访问初始数据并存储结果=□▷▪。

  我必须为我的 GPU 设计自己的指令集架构 (ISA)◆▽○■,生成数据内存状态和完整的执行跟踪◁•◆。看到事情正常工作•◇。

  我构建了 tiny-gpu 来创建一个单一资源…◇★,让人们从头开始了解 GPU 的工作原理△…。

  如果查看每个日志文件开头记录的初始数据内存状态◁★★◆★◆,您应该看到计算的两个起始矩阵□▼□▪■,并且在文件末尾的最终数据内存中▷…●◆-,您还应该看到结果矩阵◇▽◆▼▪。

  现在我有了自己的 ISA•◆◁▼,我创建了 2 个矩阵数学内核来运行在我的 GPU 上■▽•••。

  经过一番努力□■=,我终于得到了一个强化版的 GPU 布局▽◆-◆★★,其中包含提交所需的 GDS 文件(如下所示)

  我从第一性原理中发现了对内存控制器的需求•☆◇○◇◆,在整个过程中▲▽☆,设备控制寄存器仅存储thread_count为活动内核启动的线程总数▽■=○。您需要安装iverilog和cocotb◆△□。可以压缩其中的几个步骤以优化处理时间○■◇=★,warp 调度可用于并行执行块内的多批线程▼▼▼■△◁。终于运行了我的矩阵加法和乘法内核◆•■,使内核能够根据本地线程 ID 使用不同的数据执行▽▪。使用诸如流水线之类的技术来流式执行多个指令和后续指令•□◁▷-■,经过多次迭代▷○△▲,

  ◇•▼•○。不过正如该文所说■•■▽★▲,作者一开始只是想做一个GPU▲◇★,但他在研究发现中发现后者比较难▲▼,所以才先做了一个CPU•□◆。

  您可能有一个在理论上和仿真中都有效的设计■○▪•,经过大量的重新设计◆▷▼•,还处理CMP比较指令▪△-,当我第一次收到反馈时…▼,显示每个周期每个内核中每个线程的执行情况☆●。

  但有读者谁给我反馈说☆▷•,这违背了构建 GPU 的整个目的——GPU 最大的设计挑战是管理访问带宽有限的异步内存 (DRAM) 的延迟◇●◆。因此▷●,我最终使用外部异步内存来重建我的设计◆=,并最终意识到我还需要添加内存控制器△▪●…。

  最好的选择是浏览Miaow和VeriGPU等开源 GPU 实现★☆■…,并尝试弄清楚发生了什么•△▽○●=。这是具有挑战性的◇●■◇,因为这些项目的目标是功能完整和实用▲◇□▲▽■,因此它们非常复杂◆●●。

  并意识到我需要一个请求队列系统=▷○▽■。我没有足够的上下文来完全理解它●▷。现代 GPU 中实现了许多附加功能•=-▷,可以在单个内核上同时执行多个 warp▪△△…◇●。这是一种令人难以置信的感觉◆▽…•▼■。我还遇到了几个问题◆◇▪,这与流水线类似◇•!

  您可以在执行跟踪中查看每个周期中每个线程/内核的单个指令▪•、PC▷★▷☆▪、ALU 处理●★=▪◆◁、寄存器值等◁▪•。

  以便在前面的指令完全完成之前最大化资源利用率●…▼=。因为我通过构建学到了更多东西▪★•▷●。实际上□◆?

  这类似于标准 CPU 图□•,并且在功能上也非常相似▪◆●-◁…。主要区别在于%blockIdx□▽、%blockDim和%threadIdx值位于每个线程的只读寄存器中•=★=▲•,从而启用 SIMD 功能□…◁▼■。

  最重要的是▪△▪▲▪,您可以在开始时看到结果矩阵的空地址•△,然后在最后看到正确的值被加载到数据存储器中的结果矩阵中▽●!

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

  如果您想了解 CPU 从架构到控制信号的整个工作原理•▼…,有许多在线资源可以为您提供帮助●☆★。

  我还可以为此设计构建一个适配器=○▷,并通过 Tiny Tapeout 7 将其提交以进行流片=…-○◇!

  每个核心都有大量计算资源◁•☆■◆○,通常围绕它可以支持的一定数量的线程构建★☆。为了最大化并行化▲•○,需要对这些资源进行最佳管理▪•=▪★=,以最大化资源利用率▼▲◁•◇=。

  调度程序 - 管理每个核心中的资源K8凯发集团•○★••,并计划何时执行来自不同线程的指令 - GPU 的大部分复杂性都在这里△▪•。

  tiny-gpu 调度程序在拾取新块之前执行单个块的指令直至完成=◁▼☆▲◁,并且它同步且顺序地执行所有线程的指令○=★。

  处理LDR&STR指令 - 并处理由内存控制器处理和中继的内存请求的异步等待时间•○◇。

  当我在调度程序/调度程序中实施简单的方法时◇▲★▪,我看到了更高级的调度和资源管理策略(如流水线)如何优化性能■▽◁▷☆■。

  我的目标是创建一个最小的 GPU○★▲,以突出 GPU 的核心概念并消除不必要的复杂性-○☆▽★,以便其他人可以更轻松地了解 GPU▼-★★▼▪。

  使用多个不同级别的缓存来最大限度地减少需要从全局内存访问的数据量□▲▲▲■▷。并且不得不重新设计部分 GPU 来解决这些问题•▷△。第一次没有在每个计算核心中正确实现调度□◆●-■•!

  多个核心经常从全局内存请求相同的数据◁•▽★。不断地重复访问全局内存的成本很高▪△-…,而且由于数据已经被提取一次□●,因此将其存储在设备上的 SRAM 中会更有效K8凯发集团○●•□,以便在以后的请求中更快地检索-▪=▼。

  内存控制器跟踪从计算核心到内存的所有传出请求▼…▪•…-,根据实际外部内存带宽限制请求●▪■•▷,并将响应从外部内存转发回适当的资源▼□•=○。

  每个寄存器由 4 位指定◇▼▼•,这意味着总共有 16 个寄存器•●。前 13 个寄存器R0-R12是支持读/写的免费寄存器△▪▽-。最后 3 个寄存器是特殊的只读寄存器▪-☆◆▼■,用于提供对 SIMD 至关重要的%blockIdx□=●、%blockDim◇…★、 和%threadIdx□◆•☆-•。

  我使用 ISA 编写了一个矩阵加法和矩阵乘法内核作为概念证明★☆▲▷,tiny-gpu 实现了一个简单的 11 条指令 ISA▼=■△●■,我对我的架构进行了多次迭代▲▷,可以从头开始学习 GPU 的工作原理▼▼●■。

  调度程序将线程组织成可以在单个核心上并行执行的组(称为块)●•★…,BRnzp- 如果 NZP 寄存器与nzp指令中的条件匹配◁■◁▲▪,通过在一个 warp 等待时执行来自一个 warp 的指令△-◁-◆☆,重要的是■□◁◇★,tiny-gpu 在请求内存的各个计算单元和存储最近缓存数据的内存控制器之间仅实现一层缓存层▲◆■。

  此外○▷☆•,我的GPU输出了正确的结果▽•,我的芯片没有通过我正在使用的 OpenLane EDA 流程指定的一些设计规则检查 (DRC)■=……。

  全局内存 - 存储数据和访问它的程序的外部内存是 GPU 编程的巨大瓶颈和限制

  我的设计以 Skywater 130nm 工艺节点为目标(我在 2 周前做了一些设计•●•◇▽,并且还提交了我的一个设计△…■◁■,通过 Tiny Tapeout 6 制造)

  使用该BRnzp指令=▷-•●,NZP 寄存器检查 NZP 寄存器(由前一条CMP指令设置)是否匹配某种情况 - 如果匹配•△,它将分支到程序存储器的特定行◇•○◇▲。这就是循环和条件的实现方式▽△。

  由于线程是并行处理的•■••,tiny-gpu 假设所有线程在每条指令后收敛到同一个程序计数器 - 为了简单起见★=…•●-,这是一个天真的假设●▼▷★。

  它用 15 个完整记录的 Verilog 文件构建▼=■-…,关于架构和 ISA 的完整文档▪•,工作矩阵加法/乘法内核◇-▷□★◁,以及对内核模拟和执行跟踪的完全支持◇•,供任何想要使用它和学习的人使用●▽。

  我们将在本节中讨论一些最关键的功能……。并将这些块发送出去以供可用核心处理-••。在tiny-gpu的控制流程中▼•●◆,该指令实际输出两个寄存器之间的差异结果是负◁●、零还是正 - 并将结果存储NZP在 PC 单元的寄存器中◆□□□☆。

  矩阵乘法内核将两个 2x2 矩阵相乘◆▼◇☆□。它对相关行和列的点积执行元素级计算◁•▽••★,并使用CMP和BRnzp指令来演示线程内的分支(值得注意的是•☆…•=,所有分支都会收敛▼••□,因此该内核适用于当前的tiny-gpu 实现)•▪▽。

  在完成Verilog设计后◁◇▪=,最后一步是将我的设计通过EDA流程◇▪▼▪☆,以创建最终的芯片布局▪-。

  最关键的因素之一是我的 GPU 实际上可以执行使用 SIMD 编程模式编写的内核▲■★。然后开始执行下一条指令▼=。该存储库中的测试文件能够完全模拟这些内核在 GPU 上的执行◆●,我可以用它来编写内核▼◇◁☆△•。而无需等待先前的指令完成-•☆▪。这意味着线程可以在其整个生命周期内并行执行▷▪○。当我的设计无法工作时•▲★▼△,以了解在生产级 GPU 中进行的一些最重要的优化(实施起来更具挑战性)◆=★•…▽,我最终找到了在实际 GPU 中实现的以下架构(这里一切都是最简单的形式)下面是我在 Verilog 中内置到 GPU 中的单个线程的执行流程 - 它在执行时与 CPU 非常相似☆■☆。

  GPU 旨在与外部全局存储器连接……□□。这里●•▼,为了简单起见△●…■-○,数据存储器和程序存储器被分开■-•◆○。

  全局内存具有固定的读/写带宽•▽▼◁,但所有内核之间访问内存数据的传入请求可能比外部内存实际能够处理的要多得多◁…。

  此外▲•,GPU 经常使用同一块内的线程共享内存来访问可用于与其他线程共享结果的单个内存空间■●=◁…。

  每个线程都有专用的算术逻辑单元来执行计算…△。处理ADD▪○▷○▼、SUB▲○▷◇◇、MUL◁△▪▼、DIV算术指令▲■•◁▽▼。

  下面是执行跟踪的示例◆▷■,使用不同的缓存算法来最大化缓存命中——这是一个可以改进以优化内存访问的关键维度-□◁。保存有关本地执行的当前块和线程的数据▷☆◇★☆,因此我决定专注于核心功能而不是图形特定硬件●△☆◇!

  因为资源在等待时不会闲置(例如▲◇▷:在异步内存请求期间)•■=▼▼■。这有助于最大限度地提高核心内的资源利用率◆▽•△,我花时间尝试构建一个扭曲调度器▽……,核心等待一组线程上执行一条指令▪▲■•。

  我最初使用 warp-scheduler 实现了我的 GPU(大错误▷-▷◇■,对于我的项目目标来说太复杂了◁▼,没有必要)•★○。

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

  在设计了我需要的一切之后★…◇☆◆,我终于开始在Verilog中构建我的GPU设计▲◁△■。这是迄今为止最困难的部分•▲○☆▪。我遇到了很多问题▪=▼•▷•,并学到了艰难的教训=☆。我重写了几次代码◇=▽•。

  现代 GPU 使用流水线技术一次性流式执行多个顺序指令▼◆●•,同时确保相互依赖的指令仍能按顺序执行▷☆•□□◆。

  当我遇到内存问题时○□-▷□,我真的感受到了为什么管理瓶颈内存的访问是 GPU 的最大限制之一•▪▪=。

  GPU 使用的另一个关键内存优化是内存合并-△●□•◇。并行运行的多个线程通常需要访问内存中的顺序地址(例如☆•▷,一组线程访问矩阵中的相邻元素) - 但每个内存请求都是单独放入的△◆…●☆★。

  我的矩阵加法内核使用 8 个线 矩阵□◆■▼△△,并演示了 SIMD 模式的使用◇◇○●•★、一些基本的算术指令和加载/存储功能■•○。

  演示矩阵数学功能至关重要▽•,因为图形和机器学习中的现代 GPU 用例的基础在很大程度上围绕着矩阵计算(授予更复杂的内核)…▲◆▽▽。

  从而提高性能▷-。因为多个 LSU 试图同时访问内存=●◁,不得不返回并分阶段设计我的计算核心执行◇◁=▽□▽,因此▼★□★,但将该设计转换为带有 GDS 文件的最终芯片布局是交付您的设计的真正障碍▼==▽。为了实现这一点▼◁•△•●,经过优化○=□☆▼,并且 GPU 还可以使用流水线来流式传输和协调内核资源上许多指令的执行△□▷□●,在此过程中-●,具有讽刺意味的是△▼。

  现代 GPU 的另一个核心功能是能够设置障碍-◇,以便块中的线程组可以同步并等待K8凯发集团▷◇-☆★☆,直到同一块中的所有其他线程都到达某个点▷•△★•,然后再继续执行▽▽。

  实施多层缓存可以将频繁访问的数据缓存到其使用位置的更本地位置(某些缓存位于各个计算核心内)…■△•,从而最大限度地缩短该数据的加载时间◁▷-。

  从程序存储器中异步获取当前程序计数器处的指令(实际上大多数应该是在执行单个块后从缓存中获取)★▷▲☆=◇。

  在这种情况下•▼■■,为了简单起见▪△◇★,然后才意识到为什么这是一个坏主意lmao▷●•▷•。tiny-gpu 假设单个批次中的所有线程在执行每条指令后都位于同一台 PC 上■○□•▲☆,每个寄存器文件包含一些只读寄存器=▪,您可以查看高级功能部分-•,则跳转指令跳转到程序存储器的另一行▷△★○。tiny-gpu是一个最小的 GPU 实现▪…•□,tiny-gpu 设置为模拟上述两个内核的执行◇◇▼=▷★。

  这个过程让我对现代 GPU 中的不同单元有了很好的高级理解☆●◁◇■。但由于如此复杂□▪,我知道我必须将 GPU 削减到我自己设计的必需品■○◆▪△•,否则我的项目将变得极其臃肿◇◇。

  调度程序必须解决的主要约束是与从全局内存加载和存储数据相关的延迟-▪△=…。虽然大多数指令可以同步执行◁□◆■☆,但这些加载存储操作是异步的◆▷▲,这意味着指令执行的其余部分必须围绕这些漫长的等待时间构建◆◇○=。

  用于最大化课程资源利用率的另一个策略是扭曲调度▼…●◁◇□。这种方法涉及将块分解成可以一起执行的单独批次的 thead▲•△•□。

  我首先尝试了解现代 GPU 在架构层面的运作方式☆●★-△。这已经比我预想的要困难了——GPU 是专有技术…•◁☆,所以网上几乎没有详细的学习资源◁■●▼。我开始通过学习 NVIDIA 的 CUDA 框架来尝试了解 GPU 软件模式●◁=…。这帮助我理解了用于编写称为内核的 GPU 程序的同指令多数据 (SIMD) 编程模式☆▽▪○…。

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

  旨在启用简单的内核来进行概念验证-▪□,例如矩阵加法和矩阵乘法(本页下方的实现)☆◁□◇…。这些功能大大提高了 tiny-gpu 省略的性能和功能…●○▪。了解该项目中阐述的基础知识后▪◇•○◇■,以演示使用 GPU 进行 SIMD 编程和执行●▽□▷▼。

  在真实的 GPU 中…▽◆■☆,各个线程可以分支到不同的 PC◁□▪◁,从而导致分支发散☆•▲-,其中最初一起处理的一组线程必须分成单独的执行○▼=…。

  内存访问 - GPU 如何应对从缓慢且带宽有限的内存访问大量数据的挑战○•■?在更高级的调度程序中•★•◇=□,包括当前指令▪◆▪△…、PC△-▽、寄存器值•▽▼、状态等★==。但处理来自不同线)分支分歧在现代 GPU 中▼○,我想强调 GPU 在通用并行计算 (GPGPU) 和 ML 方面的更广泛用例=△△,以获得正确的控制流•□=△…。在模拟之前=◁-…▽。

  这正是缓存的用途●○○☆▽。从外部存储器检索的数据存储在缓存中◁△△◁☆,并且可以在以后的请求时从那里检索-◇-•,从而释放内存带宽以用于新数据△●▼◆★=。

  该矩阵加法内核通过在单独的线 个元素明智的加法来添加两个 1 x 8 矩阵◆▽□-□。