跳转到主要内容
瑞苏盈科专栏
我与瑞苏盈科板卡的开发故事

优化 VCK190 上 LPDDR4 与 AI 引擎之间的 GMIO 数据流

本文作者:AMD 工程师 Sonal Tamrakar

本文探讨了在 AMD Versal™ 评估板 VCK190 上,如何在 AI 引擎全局存储器 I/O (GMIO) 端口与外部存储器(例如 DDR4 和 LPDDR4)之间串流数据时实现最佳吞吐率性能。本文涵盖的三种基准测试配置为:使用 DDR4 的 VCK190 基础平台、相同基础平台但目标改为 LPDDR4,以及旨在最大程度提升 GMIO 吞吐率的自定义 LPDDR4 平台。使用 DDR4 的基础平台实现了 17,528 MB/s (17.1 GB/s) 的设计吞吐率,它受到的限制在于:GMIO 串流必须共享单个存储器控制器,这迫使读写请求排队,并阻止设计达到存储器的峰值带宽。采用交织式 LPDDR4 的基础平台实现了 17,110 MB/s (16.7 GB/s),尽管此配置跨两个 LPDDR4 控制器,但交织式地址映射导致所有 GMIO 流量都位于每个控制器的 CH0 上,使 CH1 完全未使用,从而限制了器件带宽。自定义 LPDDR4 平台实现了每个控制器 23,201.84 MB/s (22.7 GB/s) 的设计吞吐率,两个存储器控制器总计约 46,400 MB/s (45.3 GB/s),相比任一基础平台提升约 2.7 倍。

要实现此结果,需要四项关键更改协同工作:将 GMIO 接口拼块布局分散在 AI 引擎阵列的整个宽度上以分散 NMU 流量、确保全部八个 AI 引擎都采用完全线性的读写访问模式、通过针对串流优化的自定义 DDR 地址从交织式通道映射切换到非交织式通道映射,以及调整主机代码的缓冲器分配,使每个引擎的流量都落在正确的 LPDDR4 控制器上。本文会逐步介绍基线设计,解释为何需要自定义平台,并详细说明每一项优化,展示它们如何组合以交付显著改善的带宽结果。

基线 DDR4 设计

基线 DDR4 设计使用提供的 VCK190 基础平台,它通过一个共享存储器控制器将全部八条 AI 引擎 GMIO 串流都布线到板载 DDR4 存储器。不对平台作任何修改,因为这样即可提供开箱即用的 AI 引擎应用体验,此类应用以 VCK190 上的外部存储器为目标。DDR4 存储器以 3200 MT/s 的数据速率和 64 位数据总线运行,提供的理论峰值带宽达 25.6 GB/s。在实践中,该设计实现了 17,528 MB/s (17.1 GB/s) 的持续吞吐率,约为峰值的 67%。理论带宽与实测带宽之间的差距反映了通过单个存储器控制器运行八条 GMIO 串流(每条串流均执行读写)的情况。来自不同 AI 引擎的请求会导致读/写冲突并在控制器处排队,从而产生总线损耗,使控制器无法以其完整效率运行。

基线 LPDDR4 交织式设计

基线 LPDDR4 设计将 VCK190 基础平台中的 DDR4 存储器替换为 LPDDR4,并将两个板载存储器控制器配置为以交织式设置方式运行。全部八个 AI 引擎 GMIO 端口(在同时计算读写时共计 16 条串流)都布线穿越这对交织式控制器。然而,该配置仅使用每个控制器的 CH0,使两者的 CH1 都未得到使用。这是无需任何自定义即可使用 LPDDR4 的最简单途径,并可与 DDR4 基线结果进行直接比较。每个 LPDDR4 控制器以 3906 MT/s 的数据速率运行,并暴露两条 32 位通道(CH0 和 CH1),使每个控制器的峰值达到 31.2 GB/s,两个控制器的 LPDDR4 子系统总峰值达到 62.4 GB/s。在实践中,该设计实现了 17,110 MB/s (16.7 GB/s) 的持续吞吐率,约为完整峰值的 27%。限制源自两个因素。交织式地址映射将所有流量放入单个地址空间,而这与 AI 引擎产生的线性访问模式不匹配。除此之外,每个控制器上仅启用 CH0,使两个 CH1 都处于空闲状态,一半的 LPDDR4 带宽未使用。

为何需要自定义平台

两种基线设计都达到约 17 GB/s,但 LPDDR4 交织式基线仅达到 LPDDR4 子系统的 62.4 GB/s 峰值的 27%,大量带宽未得到利用。这种情况是由多个因素共同造成的。交织式配置仅启用每个控制器上一半的通道,使两个控制器上的另一条通道都处于空闲状态。平台预定义的 ROW-BANK-COLUMN 地址映射不太适合 AI 引擎的线性访问模式,从而迫使页面频繁循环打开/关闭。由于全部 16 条串流(8 条读 + 8 条写)共享单个交织式页面池,控制器花在管理页面上的时间多于移动数据的时间。

自定义 LPDDR4 平台应对这些问题的方式是将两个 LPDDR4 控制器去交织为独立的存储器(LPDDR_0 和 LPDDR_1),将 16 条串流分区为两组,每组 8 条,使每组拥有自己的控制器、bank 和页面。预定义的地址映射被替换为用于线性串流的自定义地址映射。主机代码为每个控制器分配八个连续的 512 MB 缓冲器,使每条串流获得一个线性存储器区域,从而尽可能减少页面打开/关闭循环,并且最高地址位会标识该缓冲器所属的控制器。GMIO 接口拼块在 AI 引擎阵列的整个宽度上进行布局规划,以在 VNoC 上均匀分散 NMU 流量。这些优化共同确保 16 条串流不再争夺单个共享存储器,而是改为在两个独立的存储器上运行,每个存储器各处理 8 条串流。

采用 LPDDR4 非交织模式创建 VCK190 自定义平台

VCK190 自定义平台从 VCK190 基础平台模板开始,通过扩展块设计以支持来自处理器系统 (CIPS) 和 AI 引擎阵列的 LPDDR4 存储器访问。模块框图中添加了两个额外的 AXI NoC MC(存储器控制器):axi_noc_0/LPDDR4和 axi_noc_1/LPDDR4,各自都以非交织方式驱动开发板上的一对 LPDDR4 物理通道。

每个 NoC/LPDDR4 实例都通过选择适当的存储器通道、配置系统时钟、配置 NoC 间接口以及定义自定义 DDR 地址映射来进行自定义。NoC 间从接口的数量由 AIE 设计所需的 GMIO 端口数量决定,在本例中为四个。axi_noc_0 和 axi_noc1 均配置为单存储器控制器,其 DDR 地址区域分别分配给 DDR CH1 和 DDR CH2。LPDDR4 存储器区域映射到不同的基地址(0x5000_0000_0000 和 0x6000_0000_0000),以便软件在与 AI 引擎阵列之间执行双向往来的异步数据传输时可以将缓冲器显式布局在 LPDDR4 中。

cips_noc 与 axi_noc_0/axi_noc_1 之间的连接使用 INI(NoC 间接口)端口,这是一种将 NoC 实例链接在一起的链路。cips_noc IP 具有 12 个 INI 主端口(M00_INI 到 M11_INI),均匀分配给三个下游存储器 NoC(下表提供)。axi_noc_0 和 axi_noc_1 均暴露 4 个 INI 从端口,每个 MC 一个,cips_noc 会填充所有这些端口。这意味着设计中的每个存储器控制器都有一条专用的独立路径返回主 NoC,而不是通过共享端口与其他控制器共享带宽。

此设计中的 LPDDR4 MC 使用自定义 DDR 地址映射 2BA-16RA-6CA-1BA-4CA,而不是默认的 row-bank-column 或 row-column-bank 排序。在标准映射中,bank 地址位于行字段和列字段之间,因此顺序存储器访问会反复以同一 bank 为目标。每个 bank 一次只能打开一行,因此对同一 bank 的连续访问会使存储器控制器停滞并降低吞吐率。使用自定义地址映射时,三个 bank 位被拆分到映射中的多个位置,这意味着连续的 64 字节传输会自然而然地在不同 bank 之间交替,这样在 LPDDR4 与 AI 引擎阵列之间的 GMIO 传输产生连续串流的情况下,为了维持高存储器带宽,就会对上述连续传输进行优化。

最后,在“Platform Setup”下会为存储器资源设置 SP 标签,以确保启用所有 S00–S09 AXI 端口并为其分配适当的 SP 标签,以便稍后在设计中于 Vitis 中使用。axi_noc_0 上的每个端口都标记为 LPDDR_0,axi_noc_1 上的每个端口都标记为 LPDDR_1。这些标签稍后会在 v++ 链接阶段使用,在此阶段中,系统配置文件通过名称来引用这些标签,以将每个 AI 引擎 GMIO 端口映射到特定的 LPDDR4 存储器控制器。

在创建平台之前,最后一步是确保在用户 DTB(设备树二进制对象)中预留了存储器空间。您可以在 AMD Vivado™ BD 的“Address Editor”(地址编辑器)中检查存储器地址。需要创建 system-user.dtsi 并分配 LPDDR4 区域,以便 XRT 为这些 GMIO 分配正确的物理地址。如果没有 system-user.dtsi 文件,运行时可能会发生常见的 CMA 分配错误。

AI 引擎设计

AI 引擎设计会例化八个 AI 引擎,每个引擎均由计算图模板构建,该模板以样本数、重复次数、唯一引擎 ID 和目标接口拼块位置作为参数。每个引擎包含单个直通内核,即一个基于缓冲器的复制内核,对 32 位浮点进行运算。该内核每次调用使用 256 位向量读写来处理 4,096 个样本,通过流水线优化的循环,每个周期移动八个浮点值。在重复次数为 4 的情况下,每个引擎每次计算图运行都会处理 16,384 个样本(65,536 字节)。每个引擎暴露一个输入 GMIO 端口和一个输出 GMIO 端口,两者均配置为 64 位突发宽度,服务质量 (QoS) 为 10 GB/s,形成从 LPDDR4 到 AI 引擎内核再返回 LPDDR4 的直接数据路径。每个 GMIO 端口均由其分配接口拼块中的 NoC 主单元 (NMU) 提供支持。当 GMIO 端口发出读/写时,传输事务源自 NMU,经过互连 NoC,并在目标 LPDDR4 MC 处通过 NoC 从单元 (NSU) 退出。八个引擎被分配到接口拼块 2、10、18、26、27、34、42 和 46,分散在 AI 引擎阵列的宽度上,以分散 NoC 流量并避免接口拼块争用。内核运行时比率设置为 0.9,AI 引擎编译器配置为优化级别 1,采用缓冲器优化,PL 频率为 625 MHz。虽然直通内核仅执行复制数据,但这是有意为之,因为它将 GMIO 和 LPDDR 存储器带宽隔离为唯一的性能变量,使其成为表征 GMIO 性能的理想工作负载。除 AIE 设计外,还需要一个 Vitis 系统配置文件 (system.cfg),以使用上一节中定义的 SP 标签将 AI 引擎主端口映射到 LPDDR4 存储器控制器。

AIE GMIO 布局

如上一节所述,为了分散 NoC 流量并缓解接口拼块争用,八个引擎被分配到接口拼块 2、10、18、26、27、34、42 和 46,跨越 AI 引擎阵列的整个宽度。下图呈现了应用 GMIO 约束前后的布局。结果是这八个拼块间距均匀,并且显式手动选择每个 NMU 布线穿越的接口。

实现后 NoC 视图

实现后 NoC 视图展示了在应用 GMIO 约束后,八个引擎的 GMIO 流量布线穿越 PL 和 NoC 向下到达 DDR 的方式。每条 NMU 路径都以不同的颜色标记,可以从其在 PL 边界处所分配的接口拼块开始追踪,经过 VNoC 和 HNoC,到达底部的两个 LPDDR4 控制器。下图显示了自定义前后的实现后 NoC 视图。

主机应用程序

在主机应用程序中,该程序接受一条 XCLBIN 文件路径以及一个可选的帧数变量(默认为 8192)。将 XCLBIN 加载到器件后,它会在两个 LPDDR4 存储体上分配输入和输出缓冲器;引擎 0-3 在 bank 0 上,引擎 4-7 在 bank 1 上。在每个 LPDDR4 bank 上的第二个和第三个 AI 引擎之间。然后使用浮点值初始化所有输入缓冲器。然后,该程序获取八个计算图实例的每个句柄、将其复位,并全部启动以运行固定数量的帧。异步传输在两个方向上启动:输出从 AI 引擎拉取到主机可访问的存储器中,输入从主机存储器推送到 AI 引擎中。最后,在结束时执行回归测试,将输出缓冲器与黄金参考(相同的顺序值)进行比较。以下代码段展示了如何创建缓冲器以及如何选择 LPDDR bank。

异步传输的进行方式如以下代码所示。

主机缓冲器寻址设置为每个缓冲器 512 MB,共 16 个缓冲器、8 个输入 GMIO 和 8 个输出 GMIO,全部在存储器中连续分配。LPDDR_0 的地址空间从 0x50000000000 开始,连续缓冲器之间间隔 0x20000000,即 512 MB。前 8 个缓冲器驻留在 LPDDR_0 中,其余 8 个驻留在从 0x60000000000 开始的 LPDDR_1 中。完整映射如下所示。禁用通道交织,因此每个缓冲器都干净地映射到单个通道,而不是分散在两个通道上。请注意,在 buff_samp_o_1 之后,地址前进到 0x50080000000,即同一控制器内的下一个通道,从而确认该控制器的两个通道都在使用中。

结果与分析

本节将 LPDDR4 交织式 VCK190 基础平台的吞吐率结果与上一节中描述的自定义非交织式 LPDDR4 平台进行比较。目标是强调当平台经过调整以将两个 LPDDR4 控制器完全暴露给主机应用程序时所达成的更高带宽。在 VCK190 基础平台中,两个 LPDDR4 控制器共享单个交织式地址空间,因此流量会在它们之间自动拆分,无需任何软件控制。在自定义平台中禁用交织,并添加了用户定义的地址映射,允许主机应用程序将缓冲器显式布局在 LPDDR_0 或 LPDDR_1 中,并行驱动两个控制器。

自定义非交织式平台达成了 45.3 GB/s 的读+写组合吞吐率,比交织式基础平台的 16.7 GB/s 吞吐率提升了 2.7 倍。该增益来自于独立的缓冲器区域上并行运行的两个存储器控制器,同时这两个缓冲器组之间较宽的地址间隔(0x500 对比 0x600)使流量在两个控制器之间保持平衡,并防止任一缓冲器成为 AI 引擎 GMIO 传输的瓶颈。

文章来源:AMD Xilinx开发者社区