AI引擎
Versal 系统
Versal是Xilinx推出的一种高度集成和灵活的自适应计算加速平台(ACAP,Adaptive Compute Acceleration Platform)。
Versal ACAP 是赛灵思最新一代的器件,基于台积电 7 纳米 FinFET 工艺技术。它结合了以下三个关键组件:
- 标量引擎 (Scalar Engines):代表Arm处理器系统 (PS),负责控制和管理整个系统。
- 可适应引擎 (Adaptable Engines):代表可编程逻辑 (PL),提供灵活的定制化硬件加速功能。
- 智能引擎 (Intelligent Engines):包括 AI 引擎,提供高性能的 AI 加速能力。
Arm 处理器一般 用于控制平面应用、操作系 统、通信接口和较低级别的运算或复数运算。PL 负责数据操作与传输、非矢量型计算和接口。AI 引擎一 般用于矢量实现方案中的计算密集型功能
这些组件通过高带宽的 片上网络 (NoC) 相互连接,实现高效的数据传输和协同工作。
AI 引擎是 Versal ACAP 中的一部分,用于加速 AI 运算。它由二维数组的 AI 引擎 Tile 组成,每个 Tile 包含以下组件:
Tile 互连模块 (Tile Interconnect Module):处理 AXI4-Stream 和内存映射 AXI4 的输入输出。
内存模块 (Memory Module):包含 32 KB 数据内存,分为 8 个内存库,以及内存接口、DMA 和锁。
AI 引擎 (AI Engine):一个高度优化的处理器,包含标量单元、SIMD 向量单元、地址生成单元、VLIW 功能、数据内存端口和直接流接口。
AI 引擎 graph 简介
自适应数据流 (Adaptive Data Flow, ADF) graph 是 AIE 应用的更高层次,它是一种用 C++ 编写的更高层次的 AIE 应用结构,包含节点和边缘,分别代表计算内核函数和数据连接。ADF graph 是 AIE 应用的更高层次结构,它提供了一种将多个计算内核函数组织在一起并进行数据流控制的方式 你可以把它想象成一个数据流图,其中每个节点代表一个计算内核函数,每个边代表数据流向。
ADF graph 有以下特点:
- 用 C++ 编写: ADF graph 使用 C++ 语言进行定义和描述,这使得它易于理解和使用。
- 包含节点和边缘: 节点代表计算内核函数,可以是简单的算术运算,也可以是复杂的算法。边缘代表数据流向,连接不同的节点,并指定数据传输方式。
- 静态定义、映射和编译: ADF graph 在编译时被定义、映射到硬件资源并编译成可执行代码。这意味着 graph 的结构在运行时不会发生改变。
- 数据流控制: ADF graph 提供了数据流控制机制,可以控制数据在不同节点之间的流动,并确保数据按预期顺序进行处理。
- 可扩展性: ADF graph 可以轻松地扩展,添加新的节点和边缘,以满足不同的应用需求。
举个例子,假设我们要实现一个简单的图像处理 pipeline,它包含以下步骤:
- 读取图像数据
- 对图像进行降噪处理
- 对图像进行边缘检测
- 将处理后的图像写入文件
我们可以使用 ADF graph 来描述这个 pipeline,如下所示:
// 定义一个名为 image_processing_graph 的 ADF graph
class image_processing_graph : public adf::graph {
private:
// 定义三个内核函数:read_image、denoise、edge_detection
kernel read_image;
kernel denoise;
kernel edge_detection;
public:
// 定义输入和输出端口
input_plio input;
output_plio output;
// 构造函数,初始化 graph
image_processing_graph() {
// 创建内核函数
read_image = kernel::create(read_image_kernel);
denoise = kernel::create(denoise_kernel);
edge_detection = kernel::create(edge_detection_kernel);
// 连接内核函数
connect<window<128>>(input, read_image.in[0]);
connect<window<128>>(read_image.out[0], denoise.in[0]);
connect<window<128>>(denoise.out[0], edge_detection.in[0]);
connect<window<128>>(edge_detection.out[0], output);
}
};
在这个例子中,image_processing_graph
类定义了一个 ADF graph,它包含三个内核函数:read_image
、denoise
和 edge_detection
。这些内核函数通过连接信息连接在一起,形成一个数据流 pipeline。
如何定义和创建新的graph
- 定义 graph 类
- 继承自 adf::graph 类:所有 ADF graph 类都必须是 adf::graph 类的子类。
- 定义私有成员:包含 graph 中使用的内核函数,例如 kernel first; kernel second;。
- 定义公有成员:包含 graph 的输入和输出端口,例如 input_plio in; output_plio out;。
- 实现 graph 构造函数
- 创建内核函数:使用 kernel::create() 函数创建内核函数,并指定相应的内核函数名称。
- 连接内核函数:使用 connect<>() 函数连接内核函数的输入和输出端口,并指定连接类型和窗口大小。
- 设置内核函数属性:使用 source() 和 runtime() 函数设置内核函数的源文件路径和运行时比率。
- 使用 graph
- 在主应用程序文件中包含 graph 头文件。
- 在主应用程序文件中声明 graph 对象。
- 使用 graph 的输入和输出端口与外部环境进行交互。
示例代码
#include "adf.h"
#include "kernels.h"
// 定义一个名为 my_graph 的 ADF graph
class my_graph : public adf::graph {
private:
// 定义两个内核函数:kernel_a 和 kernel_b
kernel kernel_a;
kernel kernel_b;
public:
// 定义输入和输出端口
input_plio in;
output_plio out;
// 构造函数,初始化 graph
my_graph() {
// 创建内核函数
kernel_a = kernel::create(kernel_a_function);
kernel_b = kernel::create(kernel_b_function);
// 连接内核函数
connect<window<128>>(in, kernel_a.in[0]);//窗口大小为128字节
connect<window<128>>(kernel_a.out[0], kernel_b.in[0]);
connect<window<128>>(kernel_b.out[0], out);
// 设置内核函数属性
source(kernel_a)