标量和矢量编程简介 - 2022.1 简体中文

AI 引擎内核编码 最佳实践指南 (UG1079)

Document ID
UG1079
Release Date
2022-05-25
Version
2022.1 简体中文

本节提供了有关标量和矢量处理元素的内核编程关键要素的概述。如需了解有关每个元素和最优化技巧的详细信息,请参阅后续章节。

以下示例仅使用标量引擎。其中展示了迭代穿过 512 个 int32 元素的 for 循环。每次循环迭代都执行 int32 a 和 int32 b 的单一乘法,将结果存储在 c 中并将其写入输出窗口。scalar_mul 内核会对 input_window_int32 数据的两个输出块(窗口)进行运算,并生成 output_window_int32 数据输出窗口。

API window_readincrwindow_writeincr 用于读取和写入位于内核外的圆形缓冲器。如需了解有关窗口 API 的更多详细信息,请参阅 Versal ACAP AI 引擎编程环境用户指南(UG1076) 中的窗口和串流数据 API

void scalar_mul(input_window_int32* data1,
			input_window_int32* data2,
			output_window_int32* out){
	for(int i=0;i<512;i++)
	{
		int32 a=window_readincr(data1);
		int32 b=window_readincr(data2);
		int32 c=a*b;
		window_writeincr(out,c);
	}
}

以下示例是相同内核的矢量化版本。

void vect_mul(input_window_int32* __restrict data1,
			input_window_int32* __restrict data2,
			output_window_int32* __restrict out){
	for(int i=0;i<64;i++)
	chess_prepare_for_pipelining
	{
		v8int32 va=window_readincr_v8(data1);
		v8int32 vb=window_readincr_v8(data2);
		v8acc80 vt=mul(va,vb);
		v8int32 vc=srs(vt,0);
   
		window_writeincr(out,vc);
	}
}

请注意,先前内核代码中使用的数据类型是 v8int32 和 v8acc80。窗口 API window_readincr_v8 会返回含 8 个 int32 的矢量,并将其存储在名为 vavb 的变量中。这两个变量均为矢量类型的变量,并传递至内部函数 mul,该函数会输出 v8acc80 数据类型的 vt。移位 - 舍入 - 饱和函数 srs 会对 v8acc80 类型进行缩减,该函数允许返回 v8int32 类型的 vc 变量,随后将其写入输出窗口。如需了解有关 AI 引擎支持的数据类型的更多详细信息,请参阅后续章节。

vect_mul 函数的输入和输出参数上使用的 __restrict 关键字允许通过显式声明数据之间的独立性来执行更激进的编译器最优化。

chess_prepare_for_pipelining 是编译器的编译指示,用于指令内核编译器为循环达成最优化的流水线。

此示例函数的标量版本耗时 1055 个周期,而矢量化版本仅耗时 99 个周期。如您所见,内核的矢量化版本速度提升超过 10 倍。矢量处理本身能对 int32 乘法提供 8 倍的吞吐量,但时延更高,总体无法达到 8 倍的吞吐量。但完成循环最优化后,可达到接近 10 倍。下一节详细描述了可使用的各种数据类型、可用的寄存器、以及在 AI 引擎上可达成的各种最优化,要达成这些最优化,需要使用诸如循环中的软件流水打拍和 __restrict 之类的关键字等概念。