1. 为什么我想从零设计一款AI加速器
1.1 一个朴素但真实的动机
去年我在做一个边缘端视觉推理的小项目,模型不大,参数量也就几百万级别,但部署到目标硬件上之后,帧率始终卡在十几帧上不去。我一开始以为是模型的问题,换了更轻量的骨干网络,做了剪枝和量化,效果提升有限。后来用性能分析工具一查,发现瓶颈根本不在计算单元上,而是在数据搬运——权重和中间特征在存储器和计算阵列之间来回倒腾,带宽吃满了,计算单元大部分时间在等数据。
这件事让我意识到一个很现实的问题:我们平时讨论AI芯片,张口就是算力多少TOPS、支持什么精度、跑什么模型,但真正决定实际性能的,往往是那些不那么显眼的东西——存储层次怎么设计、数据怎么复用、控制流怎么调度。这些细节在通用CPU和GPU上被厂商封装好了,你不需要关心,但一旦你想理解AI加速器到底是怎么回事,或者想自己动手做一个,这些就是绕不开的核心。
所以这篇文章,我想把"从零设计一款AI加速器"这件事完整地拆一遍。不是纸上谈兵地讲概念,而是按照一个真实的工程思路,从需求定义、架构选型、核心计算单元设计、存储层次规划,一直到控制逻辑和验证方法,把每个环节的关键决策和背后的理由讲清楚。适合谁看?如果你是对计算机体系结构有兴趣的软件工程师,或者是刚接触芯片设计、想搞明白NPU内部到底怎么运转的学生,再或者你只是好奇"矩阵乘法为什么能加速"这件事,这篇文章应该都能给你一些实在的东西。
1.2 先搞清楚:AI加速器到底在加速什么
在动手之前,得先把问题定义清楚。AI加速器不是万能芯片,它之所以能比CPU快,是因为它针对特定类型的计算做了极致的优化。那这个"特定类型的计算"是什么?
答案就是矩阵运算,尤其是矩阵乘法。无论是卷积神经网络里的卷积操作,还是Transformer里的注意力机制,拆到最底层,本质上都是矩阵乘加运算。一个卷积层可以展开成im2col形式的矩阵乘法,一个全连接层本身就是矩阵乘法,注意力里的Q乘K转置也是矩阵乘法。所以AI加速器的核心任务,就是高效地做矩阵乘法。
这里就引出一个关键概念:乘加运算(MAC,Multiply-Accumulate)。一次MAC就是a乘以b再加上c,即c = c + a * b。AI加速器里通常会有大量的MAC单元并行工作,比如一个256x256的脉动阵列,里面就有65536个MAC单元,每个时钟周期能完成65536次乘加。相比之下,CPU虽然单核频率高,但并行度有限,一个周期能做的乘加次数远不及专用的阵列结构。
但光有计算单元还不够。矩阵乘法有个特点:数据复用率高。比如C = A * B,A的每一行会和B的每一列做点积,A中的元素会被复用多次,B中的元素也是。如果每次计算都从外部存储器重新读数据,带宽根本扛不住。所以AI加速器的设计,很大程度上是在解决"怎么让数据在正确的时间出现在正确的位置"这个问题。
1.3 设计目标的量化定义
在开始画架构图之前,我习惯先把设计目标量化。不然做到一半很容易迷失方向。对于一款AI加速器,我通常会定义以下几个维度的指标:
- 峰值算力:单位是TOPS(每秒万亿次操作)或GOPS。这个数字由MAC单元数量和时钟频率决定。比如1024个MAC单元,跑在1GHz,理论峰值就是1024 * 2 * 1e9 = 2.048 TOPS(乘和加各算一次操作)。
- 能效比:单位是TOPS/W,即每瓦功耗能提供多少算力。这个指标对边缘设备尤其重要。
- 存储带宽:包括片上缓存带宽和外部存储带宽。片上带宽通常远高于外部带宽,设计时要尽量让数据在片上复用。
- 支持的精度:INT8、INT4、FP16还是混合精度。精度越低,计算单元面积越小,能效越高,但模型精度可能受影响。
- 灵活性:是只支持特定算子,还是能通过指令编程支持多种网络结构。
这几个指标之间是相互制约的。想要高算力,就得堆MAC单元,面积和功耗就上去了;想要高能效,就得降低精度或者减少数据搬运;想要灵活,就得增加控制逻辑的开销。所以设计的过程,本质上是在这些约束之间找平衡。
我给自己定的目标是:一款面向边缘推理的AI加速器,峰值算力在1 TOPS左右(INT8),能效比尽量做到5 TOPS/W以上,支持INT8和INT4两种精度,能跑常见的卷积网络和轻量级Transformer。这个目标不算激进,但足够覆盖大部分边缘场景,也足够让我把整个设计流程走通。
2. 架构选型:从CPU、GPU到NPU的思考路径
2.1 为什么不用CPU或GPU直接做
有人可能会问:既然CPU和GPU都能做矩阵运算,为什么还要专门设计AI加速器?这个问题值得认真回答,因为它直接决定了你的架构设计方向。
CPU的设计目标是通用性。它有复杂的指令流水线、分支预测、乱序执行、多级缓存,这些机制让它能高效地处理各种不同类型的任务,但代价是大量的面积和功耗花在了控制逻辑和缓存一致性上,真正用于计算的ALU(算术逻辑单元)占比很小。一个典型的CPU核心,可能只有不到10%的面积是真正的计算单元。做矩阵乘法时,CPU的向量指令(比如AVX系列)虽然能一次处理多个数据,但并行度仍然有限,而且数据在寄存器和缓存之间的搬运开销很大。
GPU的设计目标是高吞吐并行计算。它有成千上万个简单的计算核心,通过SIMT(单指令多线程)的方式并行执行。GPU做矩阵乘法确实比CPU快很多,但它的问题在于:第一,功耗高,高端GPU动辄几百瓦,边缘设备根本扛不住;第二,GPU的存储层次复杂,编程模型也复杂,对于固定的推理任务来说,很多灵活性是浪费的;第三,GPU的调度开销和内核启动开销在边缘场景下不可忽略。
NPU(神经网络处理单元)的设计思路则完全不同:它放弃通用性,专门为神经网络的计算模式做优化。具体来说,就是针对矩阵乘法的高数据复用特性,设计专用的计算阵列和存储层次,让数据流动的路径最短、复用次数最多、控制逻辑最简。这样就能在相同的面积和功耗下,获得远高于CPU和GPU的算力。
2.2 三种主流架构范式的对比
在NPU的设计中,有几种主流的架构范式,我在选型时做了详细对比:
第一种是脉动阵列(Systolic Array)。这是Google TPU采用的结构。它的核心思想是让数据像水流一样在计算单元阵列中有节奏地流动,每个计算单元从相邻单元接收数据,计算后再传给下一个单元。这种结构的优点是数据复用率极高,控制逻辑简单,适合大规模的矩阵乘法。缺点是灵活性较差,对于非规则的计算模式(比如稀疏矩阵、动态形状)支持不好。
第二种是乘加树(MAC Tree)或广播结构。这种结构中,输入数据通过广播的方式同时发送给多个计算单元,计算单元并行计算后通过加法树汇总结果。它的灵活性比脉动阵列好,但数据复用率相对低一些,因为广播本身消耗带宽。
第三种是SIMD/SIMT风格的向量处理单元。这种结构更接近GPU的思路,通过宽向量指令来并行处理数据。灵活性最高,但能效比通常不如前两种。
我最终选择了脉动阵列作为核心计算结构,理由有三:第一,我的目标场景是边缘推理,计算模式相对固定,以卷积和矩阵乘法为主,脉动阵列的高复用率正好匹配;第二,脉动阵列的控制逻辑简单,可以把更多面积留给计算单元和缓存;第三,脉动阵列的设计方法成熟,有大量的文献和开源实现可以参考,对于从零开始的设计来说,风险可控。
2.3 整体架构的顶层规划
确定了核心计算结构之后,我开始规划整体架构。一款AI加速器的完整架构通常包含以下几个部分:
- 计算核心:由多个处理单元(PE)组成的脉动阵列,负责执行矩阵乘加运算。
- 片上缓存:包括输入缓存、权重缓存和输出缓存,用于暂存参与计算的数据,减少对外部存储器的访问。
- 控制单元:负责解析指令、调度数据流、控制计算阵列的时序。
- 数据搬运引擎:负责在外部存储器和片上缓存之间搬运数据,通常支持DMA(直接内存访问)。
- 接口:包括与主机的通信接口(如PCIe或AXI)和与外部存储器的接口(如DDR控制器)。
这些部分之间的关系可以用一个简单的类比来理解:计算核心是工厂里的生产线,片上缓存是生产线旁边的物料架,控制单元是调度员,数据搬运引擎是叉车,接口是工厂的大门。生产线的效率不仅取决于机器本身的速度,还取决于物料能不能及时送到、成品能不能及时运走。
在我的设计中,计算核心是一个16x16的脉动阵列,共256个MAC单元。每个MAC单元支持INT8乘加,也支持INT4模式下的双倍吞吐。片上缓存分为三块:输入缓存(256KB)、权重缓存(256KB)和输出缓存(128KB)。控制单元采用简单的状态机加微指令的方式,支持基本的矩阵乘法、卷积和池化操作。数据搬运引擎支持双缓冲机制,可以在计算当前数据块的同时预取下一块数据。
3. 核心计算单元:脉动阵列的设计细节
3.1 脉动阵列的工作原理
脉动阵列的核心思想,是让数据在计算单元之间"脉动"式地流动。以最简单的矩阵乘法C = A * B为例,假设A是MxK的矩阵,B是KxN的矩阵,C是MxN的矩阵。在脉动阵列中,A的元素从左侧流入,B的元素从上方流入,每个计算单元在每个时钟周期完成一次乘加,并将结果累加。
具体来说,假设我们有一个NxN的脉动阵列,要计算两个NxN矩阵的乘积。在第一个时钟周期,A的第一行第一个元素和B的第一列第一个元素进入左上角的计算单元,完成一次乘加。第二个时钟周期,A的第一个元素向右移动,B的第一个元素向下移动,同时A的第二个元素和B的第二个元素进入阵列。这样,每个计算单元在每个周期都在做有用的计算,数据复用率极高。
这种结构的精妙之处在于:A的每个元素只从外部读取一次,然后在阵列中向右流动,被N个计算单元依次使用;B的每个元素也只读取一次,向下流动,被N个计算单元使用。整个计算过程中,外部存储器的访问次数大大减少,带宽压力自然就小了。
3.2 MAC单元的具体实现
MAC单元是脉动阵列的基本构件。一个典型的MAC单元包含以下部分:
- 乘法器:负责计算a * b。对于INT8乘法,可以用8x8的乘法器;对于INT4,可以用4x4的乘法器,面积更小。
- 累加器:负责将乘积累加到之前的结果上。通常是一个位宽较宽的寄存器,比如32位,以防止溢出。
- 寄存器:用于暂存输入数据和部分和,支持数据的流动。
- 控制逻辑:根据模式选择信号,决定当前是INT8模式还是INT4模式,以及是否清零累加器等。
在设计MAC单元时,有几个关键决策:
第一,乘法器的位宽和精度。INT8乘法器输出16位结果,INT4乘法器输出8位结果。为了支持两种精度,我设计了一个可配置的乘法器,在INT4模式下将两个INT4乘法器合并使用,实现双倍吞吐。这样虽然增加了一些控制逻辑,但灵活性提升明显。
第二,累加器的位宽。累加器的位宽决定了能累加多少个乘积而不溢出。对于INT8乘法,每个乘积最大是127*127=16129,如果用32位累加器,最多可以累加约26万个乘积而不溢出。对于典型的卷积层,这个范围足够了。如果担心溢出,可以在累加过程中做饱和处理,或者定期将部分和写回缓存。
第三,数据流动的方向。在脉动阵列中,数据流动的方向决定了阵列的形状和连接方式。我选择了经典的"权重固定、输入流动、部分和流动"的方式:权重在计算开始前加载到阵列中并保持不动,输入数据从左侧流入,部分和从上方流入、下方流出。这种方式适合卷积操作,因为卷积核(权重)在同一个特征图上滑动时是固定的。
3.3 阵列规模的权衡
脉动阵列的规模(即NxN中的N)是一个需要仔细权衡的参数。N越大,峰值算力越高,但面积和功耗也越大,而且对缓存容量的要求也越高。
我做了简单的估算:假设每个MAC单元的面积约为0.01平方毫米(在28nm工艺下),一个16x16的阵列就是256个MAC单元,面积约2.56平方毫米。如果换成32x32,就是1024个MAC单元,面积约10.24平方毫米。对于边缘设备来说,10平方毫米的计算核心已经相当大了,加上缓存和控制逻辑,整个芯片面积可能超过20平方毫米,成本会明显上升。
另一方面,N越大,数据复用的收益也越大。16x16的阵列,每个权重元素可以被复用16次;32x32的阵列,复用次数翻倍到32次。但复用收益是递减的,从16到32的提升,并不足以抵消面积和功耗的增加。
最终我选择了16x16的规模,峰值算力在1GHz时钟下为:256 * 2 * 1e9 = 512 GOPS(INT8),如果算上INT4模式的双倍吞吐,可以达到1 TOPS左右。这个算力对于边缘推理来说已经相当充裕了。
3.4 数据流控制与时序设计
脉动阵列的时序设计是另一个关键点。数据必须在正确的时钟周期出现在正确的位置,否则计算结果就会出错。这需要精确的控制逻辑。
我采用了一种简单的"波前"控制方式:在计算开始前,先将权重加载到阵列中;然后,在每个时钟周期,从左侧和上方分别注入输入数据和部分和;经过N个周期后,第一个输出结果从右下角流出;再经过N个周期,所有结果都流出。
为了处理不同大小的矩阵,我设计了分块计算的机制。如果矩阵的维度大于阵列的规模,就将矩阵分成NxN的块,逐块计算,块与块之间的部分和通过输出缓存暂存和累加。这样,无论矩阵多大,都可以用固定规模的阵列来处理。
注意:脉动阵列的时序设计非常容易出错,建议在RTL仿真阶段就用小规模的矩阵(比如4x4)做验证,确保数据流动的时序完全正确后,再扩展到大规模。
4. 存储层次:比计算更关键的战场
4.1 为什么存储是AI加速器的真正瓶颈
在AI加速器的设计中,有一个经常被忽视的事实:计算单元很少成为瓶颈,存储才是。这不是说计算不重要,而是说在大多数实际工作负载中,数据搬运的时间和能耗远超过计算本身。
有一个经典的估算:在28nm工艺下,一次32位浮点乘加的能耗约为3.7皮焦耳,而从外部DDR读取32位数据的能耗约为640皮焦耳,是计算的170多倍。即使是从片上缓存读取,能耗也有几十皮焦耳。这意味着,如果数据复用率不够高,加速器的能效会被存储访问严重拖累。
这就是为什么AI加速器的存储层次设计如此重要。目标很简单:让数据尽可能在离计算单元最近的地方被复用,减少对远距离存储的访问。
4.2 三级存储层次的设计
我设计了一个三级存储层次:
第一级是寄存器文件,位于每个MAC单元内部。它暂存当前参与计算的数据和部分和,访问延迟最低,能耗最小,但容量也最小。在我的设计中,每个MAC单元有少量的寄存器用于暂存输入和部分和。
第二级是片上缓存,包括输入缓存、权重缓存和输出缓存。这一级用SRAM实现,容量在几百KB级别,访问延迟在几个时钟周期,能耗远低于外部存储。输入缓存暂存从外部读取的特征图数据,权重缓存暂存卷积核参数,输出缓存暂存计算结果。
第三级是外部存储器,通常是DDR。容量大,但访问延迟高、能耗大。设计的目标是尽量减少对这一级的访问。
三级存储之间的数据搬运由数据搬运引擎控制。我采用了双缓冲机制:当计算单元在处理当前数据块时,数据搬运引擎在后台预取下一块数据到另一个缓冲区。这样,计算和数据搬运可以重叠进行,减少等待时间。
4.3 缓存容量的计算与分配
缓存容量的分配需要根据目标工作负载来定。我以典型的卷积层为例做了估算:
假设输入特征图是224x224x3(比如MobileNet的第一层),卷积核是3x3x3x32。如果每次处理一行输入(224x3个元素),需要的输入缓存约为22431字节=672字节。权重缓存需要存储33332=864个权重,约864字节。输出缓存需要存储22432个输出,约7168字节。
但这只是单层的情况。实际上,为了充分利用脉动阵列,通常需要一次处理多个输入通道和输出通道。如果一次处理16个输入通道和16个输出通道,输入缓存需要22416=3584字节,权重缓存需要331616=2304字节,输出缓存需要224*16=3584字节。
考虑到需要双缓冲,以及支持更大的特征图和更多的通道数,我将输入缓存和权重缓存各设为256KB,输出缓存设为128KB。这个容量在边缘设备上是可以接受的,SRAM的面积大约在几平方毫米级别。
4.4 数据复用策略的优化
存储层次设计好了,接下来就是怎么最大化数据复用。我采用了以下几种策略:
权重复用:权重在脉动阵列中保持不动,被所有输入数据复用。这是脉动阵列的天然优势。
输入复用:同一块输入数据可能与多个不同的卷积核做卷积,产生多个输出通道。我将输入数据缓存在输入缓存中,供多个输出通道的计算复用。
输出复用:在分块计算中,部分和需要在块与块之间累加。我将部分和暂存在输出缓存中,避免频繁写回外部存储器。
通道分组:对于通道数很多的层,我将通道分组,每组内的数据在片上完成所有计算后再写回。这样可以减少中间结果的搬运。
这些策略的组合使用,可以将外部存储器的访问量降低一到两个数量级,从而大幅提升能效比。
5. 控制逻辑与指令集设计
5.1 控制单元的角色
控制单元是AI加速器的"大脑",负责解析指令、调度数据流、控制计算阵列的时序。它的设计目标是在保证功能正确的前提下,尽量简单,因为复杂的控制逻辑会消耗面积和功耗。
我采用了一种"微指令+状态机"的控制方式。微指令是一条条简单的控制信号组合,比如"从输入缓存读取一行数据"、"启动脉动阵列计算"、"将结果写入输出缓存"等。状态机根据当前执行的阶段,依次发出这些微指令。
5.2 指令集的设计
指令集是控制单元与软件之间的接口。我设计了一套简单的指令集,包含以下几类指令:
- 配置指令:设置计算模式(INT8/INT4)、矩阵维度、缓存地址等参数。
- 数据搬运指令:控制DMA在外部存储器和片上缓存之间搬运数据。
- 计算指令:启动脉动阵列执行矩阵乘法或卷积。
- 同步指令:等待某个操作完成,或者设置屏障。
指令的格式我设计得比较紧凑,每条指令32位,包含操作码、操作数和一些控制位。指令序列由编译器或手动编写,存储在指令缓存中,由控制单元依次读取和执行。
5.3 卷积操作的映射
卷积是AI加速器最常执行的操作之一。将卷积映射到脉动阵列上,需要一些技巧。
最常用的方法是im2col:将输入特征图按照卷积核的感受野展开成矩阵,然后做矩阵乘法。比如一个3x3的卷积核在5x5的输入上滑动,可以展开成一个9x9的矩阵(每个位置取9个元素),然后与9x1的卷积核向量做矩阵乘法。
但im2col会增加存储开销,因为输入数据被重复存储了。另一种方法是直接做卷积,让脉动阵列的输入数据按照卷积的滑动窗口方式流动。这种方式更节省存储,但控制逻辑更复杂。
我选择了im2col的方式,因为它实现简单,而且可以复用矩阵乘法的硬件。虽然存储开销大一些,但通过合理的分块和缓存策略,可以控制在可接受的范围内。
5.4 控制流的优化
控制流的优化目标是减少空闲周期,让计算单元尽可能保持忙碌。我采用了以下几种方法:
流水线化:将数据搬运、计算和结果写回分成三个阶段,让它们重叠执行。当计算单元在处理第i块数据时,DMA在搬运第i+1块数据,同时第i-1块的结果在写回。
预取:根据指令序列,提前将下一块需要的数据从外部存储器预取到片上缓存。
乱序执行:对于没有数据依赖的指令,允许乱序执行,以提高并行度。但这会增加控制逻辑的复杂度,我在第一版设计中暂时没有采用。
提示:控制逻辑的验证是设计中最耗时的部分之一。建议在RTL仿真中构造各种边界情况,比如空矩阵、单元素矩阵、维度不匹配的矩阵等,确保控制逻辑在所有情况下都能正确工作。
6. 验证方法与常见问题排查
6.1 功能验证的基本流程
AI加速器的验证是一个系统工程,需要从模块级到系统级逐步验证。
模块级验证:对每个模块(MAC单元、缓存、DMA、控制单元)单独验证。比如MAC单元,需要验证INT8和INT4模式下的乘法结果是否正确,累加器是否会溢出,清零信号是否有效等。
集成验证:将各个模块连接起来,验证它们之间的接口和时序。比如验证DMA能否正确地将数据写入缓存,控制单元能否正确地启动计算阵列等。
系统级验证:在完整的加速器上运行真实的神经网络模型,比如MobileNet或ResNet的一个层,将结果与CPU上的参考实现对比,确保数值一致。
我使用Verilog编写RTL代码,用Verilator做仿真,用Python生成测试向量和参考结果。这种组合灵活且开源,适合个人开发者。
6.2 常见问题与排查技巧
在设计过程中,我遇到了不少问题,这里整理成速查表:
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 计算结果全为零 | 权重未正确加载 | 检查权重加载时序 | 确保权重在计算前已写入阵列 |
| 计算结果部分错误 | 数据流动时序错误 | 用小规模矩阵仿真 | 调整数据注入的时钟周期 |
| 累加结果溢出 | 累加器位宽不足 | 检查累加器位宽和累加次数 | 增加位宽或定期写回部分和 |
| 能效比低于预期 | 外部存储访问过多 | 统计DDR访问次数 | 优化数据复用策略,增加缓存容量 |
| 控制逻辑死锁 | 状态机状态转换错误 | 检查状态机的所有转换路径 | 增加超时机制或修复状态转换 |
| 仿真速度过慢 | 测试向量过大 | 减小测试规模 | 先用小规模验证,再逐步扩大 |
6.3 性能评估与迭代
设计完成后,需要对性能做评估。我主要关注以下几个指标:
峰值算力利用率:实际算力与峰值算力的比值。如果利用率低于50%,说明数据供应或控制流有问题。
能效比:实际功耗与算力的比值。我通过仿真估算动态功耗,包括计算单元、缓存和DMA的功耗。
面积:通过综合工具估算芯片面积,确保在目标工艺下可接受。
根据评估结果,我做了几轮迭代:第一轮优化了数据复用策略,将DDR访问量降低了60%;第二轮优化了控制流,将空闲周期减少了30%;第三轮调整了缓存容量,在面积和性能之间找到了更好的平衡点。
6.4 从设计到实现的注意事项
如果你也想从零设计一款AI加速器,有几个坑我想提前提醒:
第一,不要一开始就追求大而全。先做一个能跑通的小规模设计,比如4x4的阵列,验证整个流程,然后再逐步扩展。我见过太多人一开始就设计32x32的阵列,结果在验证阶段卡住,最后不了了之。
第二,存储层次的设计要尽早考虑。不要等到计算单元设计完了才想缓存的事。存储和计算是紧密耦合的,需要协同设计。
第三,验证的重要性不亚于设计。一个没有经过充分验证的设计,即使逻辑看起来正确,也可能在实际运行中出错。建议把至少40%的时间花在验证上。
第四,善用开源工具和参考设计。有很多开源的NPU项目和论文可以参考,比如NVDLA、Gemmini等。不要从零造轮子,站在巨人的肩膀上效率更高。
第五,关注工艺和功耗。设计出来的东西最终要流片或部署到FPGA上,工艺决定了面积和功耗的上限。在设计的早期阶段就要对目标工艺有清晰的认知。
7. 一些个人体会与后续扩展方向
做这个项目的过程中,我最大的体会是:AI加速器的设计,本质上是一个"数据搬运"的问题,而不是"计算"的问题。计算单元的设计相对直接,无非是乘法器和加法器的组合,但怎么让数据高效地流动、复用、暂存,才是真正考验设计功力的地方。脉动阵列之所以经典,就是因为它用简单的结构解决了数据复用的问题。
另一个体会是,软硬件协同设计非常重要。加速器的指令集、数据布局、算子映射方式,都会影响最终的性能。如果只关注硬件而忽略软件,设计出来的加速器可能很难用;如果只关注软件而不了解硬件,也无法充分发挥硬件的潜力。
后续如果继续深入,我会考虑几个方向:一是支持稀疏计算,利用神经网络的稀疏性进一步提升能效;二是支持动态形状,适应不同输入尺寸的模型;三是探索存内计算等新型存储技术,从根本上解决数据搬运的瓶颈;四是完善编译器和工具链,让开发者能更方便地把模型部署到加速器上。
这个项目让我对计算机体系结构有了更深的理解,也让我意识到,很多看似复杂的技术,拆解开来都是一些朴素的思想加上大量的工程细节。如果你也对这方面感兴趣,建议从一个小规模的设计开始,动手做一遍,比看十篇文章都有用。