VTA 硬件指南
我们提供了 VTA 硬件设计的自上而下的概述。本硬件设计指南包含两个级别的 VTA 硬件:
- VTA 设计及其 ISA 硬件-软件接口的架构概述。
- VTA 硬件模块的微架构概述,以及计算 core 的微代码规范。
VTA 概述
VTA 是一种通用深度学习加速器,专为快速高效的密集线性代数而构建。VTA 包含一个简单的类似 RISC 的处理器,可以在秩(rank)为 1 或 2 的张量寄存器上执行密集线性代数运算。此外,该设计采用解耦的访问执行,来隐藏内存访问延迟。
在更广的范围内,VTA 可以作为全栈优化的模板深度学习加速器设计,将通用张量计算接口提供给编译器栈。
上图给出了 VTA 硬件组织的高级概述。VTA 由四个模块组成,它们通过 FIFO 队列和本地内存块(SRAM)相互通信,实现任务级 pipeline 并行:
- fetch 模块负责从 DRAM 加载指令流。它将这些指令解码,并将它们路由到三个命令队列的任意一个。
- load 模块负责将输入和权重张量从 DRAM 加载到数据专用的芯片存储器中。
- compute 模块用其 GEMM 内核执行密集线性代数计算,并用其张量 ALU 执行一般计算。它还负责将数据从 DRAM 加载到寄存器文件中,并将微操作内核加载到微操作缓存中。
- store 模块将计算 core 产生的结果存储回 DRAM。
HLS 硬件源代码组织
VTA 设计目前在 Vivado HLS C++ 中指定,只有 Xilinx 工具链支持。 VTA 硬件源代码包含在 3rdparty/vta-hw/hardware/xilinx/sources
目录下:
vta.cc
包含所有 VTA 模块的定义,以及顶层 VTA 设计的顶层行为模型。vta.h
包含用 Xilinxap_int
类型实现的类型定义,以及函数原型声明。
此外,预处理器宏定义在 3rdparty/vta-hw/include/vta/hw_spec.h
目录下。这些宏定义大部分来自 3rdparty/vta-hw/config/vta_config.json
文件中列出的参数。
json 文件由 3rdparty/vta-hw/config/vta_config.py
处理,生成一个编译标志的字符串,来定义预处理器的宏。
makefile 文件用这个字符串,在 HLS 硬件综合编译器和构建 VTA runtime 的 C++ 编译器中,设置这些高级参数。
HLS 模块示例
以下是一个 VTA 模块在 C++ 中的定义:
void fetch(
uint32_t insn_count,
volatile insn_T *insns,
hls::stream<insn_T> &load_queue,
hls::stream<insn_T> &gemm_queue,
hls::stream<insn_T> &store_queue) {
#pragma HLS INTERFACE s_axilite port = insn_count bundle = CONTROL_BUS
#pragma HLS INTERFACE m_axi port = insns offset = slave bundle = ins_port
#pragma HLS INTERFACE axis port = load_queue
#pragma HLS INTERFACE axis port = gemm_queue
#pragma HLS INTERFACE axis port = store_queue
#pragma HLS INTERFACE s_axilite port = return bundle = CONTROL_BUS
INSN_DECODE: for (int pc = 0; pc < insn_count; pc++) {
#pragma HLS PIPELINE II = 1
// Read instruction fields
// 读取指令字段
insn_T insn = insns[pc];
// Do some partial decoding
// 做部分解码
opcode_T opcode = insn.range(VTA_INSN_MEM_0_1, VTA_INSN_MEM_0_0);
memop_id_T memory_type = insn.range(VTA_INSN_MEM_5_1, VTA_INSN_MEM_5_0);
// Push to appropriate instruction queue
// 推送到合适的指令队列
if (opcode == VTA_OPCODE_STORE) {
store_queue.write(insn);
} else if (opcode == VTA_OPCODE_LOAD &&
(memory_type == VTA_MEM_ID_INP || memory_type == VTA_MEM_ID_WGT)) {
load_queue.write(insn);
} else {
gemm_queue.write(insn);
}
}
}
关于 HLS 编码的一些观点:
- 参数:每个函数的参数列表和接口编译指示,定义了生成的硬件模块公开的硬件接口。
- 按值传递的参数表示的是,主机可以写入的只读硬件内存映射寄存器。例如,这个 fetch 函数有一个
insn_count
参数,该参数将被合成为主机写入的内存映射寄存器,设置给定 VTA 指令序列的长度。 - 根据所用的接口编译指示,指针参数可以是:
- 与
m_axi
接口编译指示一起使用时,将生成 AXI 请求者接口,提供对 DRAM 的 DMA 访问。 - 与
bram
接口编译指示一起使用时,生成 BRAM 接口,将读和/或写端口公开到 FPGA block-RAM。
- 与
- 将推理传递的 HLS 流与
axis
接口编译指示结合,产生模块的 FIFO 接口。硬件 FIFO 在模块之间提供了一种有用的同步机制。
- 按值传递的参数表示的是,主机可以写入的只读硬件内存映射寄存器。例如,这个 fetch 函数有一个
- 编译指示*(pragmas)*:要定义每个模块的硬件实现,编译器编译指示是必不可少的。下面列出了 VTA 设计中使用的几个编译指示,其作用是将实现要求传递给编译器。
HLS INTERFACE
:指定合成硬件模块的接口。HLS PIPELINE
:通过设置启动间隔目标来定义硬件 pipeline 性能 target。当设置II == 1
target 时,它告诉编译器合成的硬件 pipeline 能在每个周期执行一次循环迭代。HLS DEPENDENCE
:指示编译器忽略给定循环中某些类型的依赖检查。一个对相同 BRAM 结构进行写和读的循环体,需要 II 为 1。HLS 编译器必须假设最坏的情况,即:向之前写操作更新循环的地址发出读操作:鉴于 BRAM 时序特性,这是无法实现的(至少需要 2 个周期才能看到更新的值)。因此,为了实现 II 为 1,必须放宽依赖检查。注意,当打开此优化时,它会进入软件堆栈,防止写入后读取相同的地址。
备注
本 参考指南 给出了 Xilinx 2018.2 工具链更深入、更完整的 HLS 规范。