AIE微信合集
AIE(1)
对于Versal,我们从系统角度看,可将其分为3个Domain:AIE、PS和PL,如下图所示。如果要运行一个AIE的应用,绝大多数情况下,这3个Domain我们都会用到,使其协同工作。这里我们仅关注AIE Domain,将介绍如何使用Vitis 2021.2创建AIE应用工程。
File->New->Application Project。
图中标记1:simple_application将是我们在AIE上运行的工程。标记2:文件夹data,这里存放了仿真需用的输入文件input.txt和输出参考文件golden.txt。标记3:文件夹src,这里存放了设计需用的各种源文件,还包括一个子目录标记4:Kernels,用于存放描述Kernel的源文件。
project.h创建了一个类simpleGraph。
实际上,仿真平台与graph之间的连接关系可用下图表述。
AIE(2) graph
自适应数据流 (Adaptive Data Flow, ADF) 计算图是 AIE 应用的更高层次。这些计算图是以 C++ 编写的,包含节点和边缘,其中节点表示计算内核函数和/或子计算图,边缘表示数据连接。所有 ADF 计算图类定义都是预定义的数据流计算图类 adf::graph 的子类,必须位于头文件内。
AIE(3) kernel
需用注意的是多个Kernel可运行在同一个AIE上,但是一个Kernel不能运行在多个AIE上,这就要求设计者对函数进行合理的分割,以保证每个Kernel至多仅占用一个AIE。
AIE要求所有的Kernel都是void。
数据访问机制
在这个案例中,Kernel访问数据是通过window方式。另一种方式为stream。
基于window的数据访问方式意味着Kernel直接从当前AIE的本地Memory(32KB)或与之相邻的AIE的本地Memory读取数据。只有当window写满时,Kernel才会将数据加载到AIE中。这意味着在第一次调用Kernel时会有一些延迟,因为填满window需用一定的时间。但是,由于采用的是乒乓缓冲,当Kernel在运行的过程中,下一套数据就可以写入Memory,这样当再次调用Kernel时,数据已准备就绪了。
基于stream的数据访问方式意味着Kernel直接从AIX-Stream接口读取数据。在这种情况下,Kernel按采样方式读取数据。采用stream方式可能会造成上游Kernel反压。这是因为下游Kernel处理数据还不够快。也可能会造成下游Kernal停滞,这是因为上游Kernel不能更快的提供数据。
AIE(6)—用Vitis Analyzer查看AIE编译结果
在Explorer窗口中,双击文件project.aiecompile_summary(位于目录Emulation-AIE/Work),即可打开Vitis Analyzer。
或者只是$vitis_analyzer 打开project.aiecompile_summary
Graph视图
点击Vitis Analyzer左侧控制面板的Graph即可呈现graph视图,如下图所示。从这个视图中可以看到kernel的连接关系以及kernel之间的memory(对应图中的buf)。从输入到第一个kernel使用了双缓存buf0和buf0d(double buffers,字母d的来历),本质上就是乒乓操作。同样地,从第二个kernel到输出也使用了双缓存buf2和buf2d。两个kernel之间使用了单缓存buf1,这是因为这两个kernel是按顺序执行,不会同时访问同一个memory。Graph视图底部的表格显示了具体kernel信息,例如first为kernel的实例化名字,simple为描述kernel功能的C++函数名,这个kernel运行在AIE上,AIE位于AIE阵列的第25列第0行。表格和Graph视图是相互关联的,例如在表格中选中first,Graph中的first就会以高亮方式显示。
选择上图中红色方框内的Tile View,则整个Graph视图将显示为下图所示方式。从图中可以看到first和second这两个kernel位于AIE Tile [25, 0],这意味着这两个kernel在同一个AIE上顺序执行。还可以看到这两个kernel访问的memory位于AIE Tile[24,0]和[25,1],分别位于[25,0]的左侧和上方,与[25,0]是相邻位置,故不需要任何DMA。
AIE(7)—理解Runtime Ratio
增加runtime ratio使得每个kernel运行在不同的AIE Core上有可能提升graph吞吐率,但也增加了AIE的利用率。
降低runtime ratio有可能会降低AIE的利用率。这是因为只有当多个kernel确实可以运行在同一个AIE上时,工具才会将其映射到同一个AIE Core上。
AIE(8)—创建一个包含PL/PS/AIE的Vitis工程(1)
在此基础上,可得到platform与graph的连接关系,如下图所示。
Hardware link Application
那么AIE与PL之间究竟如何连接呢?这就要看HW Link部分。
生成XCLBIN之后,我们也可以看到生成该文件的Vivado工程,工程名为prj.xpr,具体路径如下图所示。
AIE(12)—AI Engine架构概览
AI Engine阵列是由一系列的AI EngineTile构成。每个AI Engine Tile包含一个AI Engine,一个存储单元和一个互连单元,如下图所示。可以看到相邻两行AI Engine Tile的存储单元与AI Engine的位置正好相反。
互连单元采用AIX4 Stream接口将数据在东西南北四个方向传送。同时每个AI Engine Tile的存储单元都包含一个DMA。每个DMA由一个独立的S2MM和一个独立的MM2S构成。前者用于将数据从Stream上取下来写入到存储单元,后者用于将存储单元的数据上传到Stream,如下图所示。
AIE(17)—更新RTP(1)
AIE Kernel有时需要由外部提供参数更新kernel行为,此时就要用到RTP(Run-Time Parameter)。
AIE支持两种类型的RTP,一种是异步(Asynchronous),通常由PS或其他AIE Kernel控制。“异步”意味着RTP可以随时被更改。在每次Kernel被调用时,RTP都会被读取,而不会进行任何同步处理。这种机制适合于参数不经常更新的场合,例如滤波器系数。另一种为同步RTP(Synchronous)。“同步”意味着只有当处理器将RTP传递给AIE Kernel后,该Kernel才能被触发执行。无论是哪种类型,RTL都可以是标量(Scalar)或数组(Array)。
我们先看一个同步RTP。系统框图如下图所示。图中sine为AIE Kernel,其中trigger为RTP,此处为标量。s2mm为HLS Kernel,最终通过HLS在PL侧实现。
如前所述,RTP也可以是数组。我们看一下数组为RTP的一个例子。如下图所示,HLS Kernel random_noise产生输入数据传递给AIE Kernel fir24_sym。fir24_sym是一个滤波器,滤波器系数作为输入参数由PS传递,其输出经HLS Kernel s2mm写入到外部存储器。这里既用到了AIE API(对应AIE Kernel),又用到了OpenCL API(对应Host)。
AIE(19)—Packet Switching(1)
多个stream数据流可以共享一个物理通道,这个物理通道可以是PL到AIE也可以是AIE到PL。这样的好处是节省了PL接口,尤其适用于低带宽的场合。
本质上,packet switching使用了一对解复用器(de-multiplexer)和复用器(multiplexer)。前者将打包的数据流根据packet ID分配给不同的kernel,后者将来自于不同kernel上的数据流合并汇聚为一个数据流。为此,在ADF graph library中引入了pktsplit和pktmerge。pktsplit是一个1:n的解复用器,pktmerge是一个n:1的复用器。n最大值为32。
我们通过一个具体案例来体会一下packet switching的使用方法。这个例子包含4个AIE kernel,每个kernel的输入/输出均采用Window-based方式。4个Kernel的输入数据分别来自于pktsplit解复用器的4个输出,而4个Kernel的输出数据则通过pktmerge复用器合并为一路输出数据。需要注意的是将packet stream与window连接时connect里填的参数分别为pktstream和window,如下图中红色方框所示。
Packet sender由两部分构成。第一部分用于生成packet header,第二部分则是将4路数据分时发送到PLIO stream上。本质上就是分时复用一个PLIO channel。如下图所示。
再看packet receiver。Packet receiver收到的是一个打包好的数据流,因此,它需要根据packet header提供的信息将数据分发到不同的stream上。具体代码如下图所示。
无论是packet sender还是packet receiver,都需要获知packet ID和stream的对应关系,这可由packet_ids_c.h文件获取,如上述代码中的深蓝色方框所示。这个文件是在AIE编译完之后生成的。因此,一旦AIE侧发生改变就需要重新编译生成此文件。