本文档介绍了TFHE中用于连接HPU加速器的tfhe-hpu-backend代码。它包含一个HpuDevice抽象,可以轻松配置和调度HPU加速器上的TFHE操作。提供了HPU硬件设置、CPU密文克隆到HPU、在HPU上调度操作的示例以及预先构建的示例。
tfhe-hpu-backend
包含了与TFHE的HPU加速器交互的代码。
它包含一个HpuDevice
抽象,可以轻松地在HPU加速器上配置和调度TFHE操作。
用户API公开了以下用于硬件设置的函数:
HpuDevice::new
, HpuDevice::from_config
: 从配置文件实例化抽象设备。HpuDevice::init
: 配置并上传所需的公共材料。new_var_from
: 从 tfhe-rs
密文创建一个HPU密文。HPU设备也可以在 integer
中使用,借助以下函数:
tfhe::integer::hpu::init_device
: 使用服务器密钥初始化给定的HPU设备。tfhe::integer::hpu::ciphertext::HpuRadixCiphertext::from_radix_ciphertext
: 将CpuRadixCiphertext转换为其HPU对应形式。通过设置线程局部HPU服务器密钥,也可以从 hl-api
无缝使用HPU设备:
tfhe::Config::from_hpu_device
: 从HpuDevice提取hl-api配置。tfhe::set_server_key
: 在当前线程中注册Hpu服务器密钥。借助 hw-xfer
功能,还可以从 high-level-api
对象创建HPU变量。
这实现了一个trait,可以在HPU加速器上 clone_on
、mv_on
FheUint
对象,并从它们那里 from
转换回来。
这些对象实现了 std::ops
trait,可用于在HPU硬件上调度操作。
tfhe-hpu-backend
分为多个模块:
entities
: 定义由HPU加速器处理的结构。从/到这些对象的转换trait在 tfhe-rs
中实现。asm
: 描述了HPU的类似汇编的语言。它实现了抽象的HPU行为,并通过微代码轻松更新它。fw
: 帮助微代码设计者的抽象。使用简单的rust程序来描述新的HPU操作。帮助进行寄存器/堆管理。interface
:
device
: 公开用户API的高级结构。backend
: 包含HPU模块的内部私有结构variable
: 包装HPU密文。它使硬件对象的生命周期可以在 rust
借用检查器中Hook。memory
: 处理板载内存分配和同步config
: 帮助通过TOML配置文件配置HPU加速器cmd
: 将 variable
上的操作转换为具体的HPU命令regmap
: 轻松与HPU内部寄存器通信。rtl
: 定义从HPU的状态/配置寄存器填充的具体 rust
结构下面是后端内部结构的概述。
这张图描述了 tfhe-hpu-backend
的内部模块,Device是用户的主要入口点。其生命周期如下:
创建HpuDevice,打开与相关FPGA的链接。配置相关的驱动程序并上传比特流。读取FPGA寄存器以提取支持的配置和功能。构建固件转换表(IOp -> DOps流)。
在板载内存中分配所需的内存块。上传TFHE计算所需的公共材料。
创建处理TFHE密文的HPU变量。它使用所需的内部资源包装TFHE密文,并强制执行正确的生命周期管理。此抽象强制在变量生命周期内所有必需的资源都是有效的。
用户可以从HPU变量触发HPU操作。 Variable抽象强制要求在硬件上正确同步所需的对象,并将每个操作转换为具体的HPU命令。 当硬件确认HPU操作时,将更新相关变量的内部状态。 这种机制支持异步操作,并最大程度地减少主机到/从HW的内存传输量。 这种机制还支持将计算图卸载到HPU,并且仅需要在最终结果上进行同步。
HPU配置旋钮收集在TOML配置文件中。该文件描述了目标FPGA及其关联的配置:
[fpga] # FPGA目标
# FPGA中的寄存器布局
regmap=["${HPU_BACKEND_DIR}/config_store/${HPU_CONFIG}/hpu_regif_core_cfg_1in3.toml",
"${HPU_BACKEND_DIR}/config_store/${HPU_CONFIG}/hpu_regif_core_cfg_3in3.toml",
"${HPU_BACKEND_DIR}/config_store/${HPU_CONFIG}/hpu_regif_core_prc_1in3.toml",
"${HPU_BACKEND_DIR}/config_store/${HPU_CONFIG}/hpu_regif_core_prc_3in3.toml"]
polling_us=2
[fpga.ffi.V80] # 硬件属性
id="${V80_PCIE_DEV}"
board_sn="${V80_SERIAL_NUMBER}"
hpu_path="${HPU_BACKEND_DIR}/config_store/v80_archives/psi64.hpu"
ami_path="${AMI_PATH}/ami.ko"
qdma_h2c="/dev/qdma${V80_PCIE_DEV}001-MM-0" # QDma 主机到卡设备
qdma_c2h="/dev/qdma${V80_PCIE_DEV}001-MM-1" # QDma 卡到主机设备
[rtl] # RTL 选项
bpip_used = true # BPIP/IPIP 模式
bpip_use_opportunism = false # 使用严格的刷新范例
bpip_timeout = 100_000 # 时钟周期中的BPIP超时
[board] # 板配置
ct_mem = 32768 # 分配的密文数量
ct_pc = [ # 用于密文的内存
{Hbm= {pc=32}},
{Hbm= {pc=33}},
]
heap_size = 16384 # 为堆保留的插槽数
lut_mem = 256 # 分配的LUT表数量
lut_pc = {Hbm={pc=34}} # 用于LUT的内存
fw_size= 16777216 # 固件转换表的大小(以字节为单位)
fw_pc = {Ddr= {offset= 0x3900_0000}} # 用于固件转换表的内存
bsk_pc = [ # 用于自举密钥的内存
{Hbm={pc=8}},
{Hbm={pc=12}},
{Hbm={pc=24}},
{Hbm={pc=28}},
{Hbm={pc=40}},
{Hbm={pc=44}},
{Hbm={pc=56}},
{Hbm={pc=60}}
]
ksk_pc = [ # 用于密钥切换密钥的内存
{Hbm={pc=0}},
{Hbm={pc=1}},
{Hbm={pc=2}},
{Hbm={pc=3}},
{Hbm={pc=4}},
{Hbm={pc=5}},
{Hbm={pc=6}},
{Hbm={pc=7}},
{Hbm={pc=16}},
{Hbm={pc=17}},
{Hbm={pc=18}},
{Hbm={pc=19}},
{Hbm={pc=20}},
{Hbm={pc=21}},
{Hbm={pc=22}},
{Hbm={pc=23}}
]
trace_pc = {Hbm={pc=35}} # 用于跟踪日志的内存
trace_depth = 32 # 为跟踪日志分配的内存大小(以MiB为单位)
[firmware] # 固件属性
implementation = "Llt" # 要使用的固件风格
integer_w=[4,6,8,10,12,14,16,32,64,128] # 支持的IOp宽度列表
min_batch_size = 11 # 最大吞吐量的最小批处理大小
kogge_cfg = "${HPU_BACKEND_DIR}/config_store/${HPU_CONFIG}/kogge_cfg.toml"
custom_iop.'IOP[0]' = "${HPU_BACKEND_DIR}/config_store/${HPU_CONFIG}/custom_iop/cust_0.asm"
## 默认固件配置。可以在每个IOp的基础上进行编辑
[firmware.op_cfg.default]
fill_batch_fifo = true
min_batch_size = false
use_tiers = false
flush_behaviour = "Patient"
flush = true
以下代码段显示了如何实例化和配置 HpuDevice
:
// 以下代码片段使用HighLevelApi抽象
// 实例化 HpuDevice --------------------------------------------------
let hpu_device = HpuDevice::from_config(&args.config.expand());
// 生成密钥 ----------------------------------------------------------
let config = Config::from_hpu_device(&hpu_device);
let cks = ClientKey::generate(config);
let csks = CompressedServerKey::new(&cks);
// 将 HpuDevice 和密钥注册为线程局部引擎
set_server_key((hpu_device, csks));
以下代码段显示了如何将CPU密文转换为HPU密文:
// 抽取随机值作为输入
let a = rand::thread_rng().gen_range(0..u8::MAX);
// 在Cpu端加密它们
let a_fhe = FheUint8::encrypt(a, &cks);
// 克隆一个密文并将它们移动到HpuWorld中
// NB: 在此阶段,数据不会通过Pcie移动
// 数据仅在Hpu中排序,并复制到主机内部缓冲区中
let a_hpu = a_fhe.clone_on(&hpu_device);
一旦注册为线程局部引擎,HighLevel FheUint将转换为Hpu格式。 以下代码段显示了如何在HPU上启动操作:
// 求和 -------------------------------------------------------------
// 生成随机输入值并计算预期结果
let in_a = rng.gen_range(0..u64::max_value());
let in_b = rng.gen_range(0..u64::max_value());
let clear_sum_ab = in_a.wrapping_add(in_b);
// 加密输入值
let fhe_a = FheUint64::encrypt(in_a, cks);
let fhe_b = FheUint64::encrypt(in_b, cks);
// 通过hl_api触发HPU上的操作
let fhe_sum_ab = fhe_a+fhe_b;
// 解密值
let dec_sum_ab: u64 = fhe_sum_ab.decrypt(cks);
tfhe/examples/hpu
中已经有一些示例应用程序可用:
为了在硬件上运行这些应用程序,用户必须从项目根目录(即 tfhe-rs-internal
)使用 hpu-v80
功能进行构建:
注意:运行示例需要正确拉取
.hpu
文件。由于这些文件的大小,它们由git-lfs支持,并且默认情况下处于禁用状态。 为了检索它们,请从 TFHE-rs 根文件夹运行以下命令:make pull_hpu_files
$ cargo build --release --features="hpu-v80" --example hpu_hlapi --example hpu_bench
## 使用 setup_hpu.sh 脚本正确设置环境
$ source setup_hpu.sh --config v80 -p
## 引入 Xilinx 环境(2024 或 2025 版本)
$ source /opt/xilinx/Vivado/2024.2/settings64.sh
$ xsdb -eval "connect;puts [lsort -unique [regex -all -inline {( XFL[A-Z0-9]*)} [targets -target-properties]]]"
****** Xilinx hw_server v2024.2
**** Build date : Oct 29 2024 at 10:16:47
** Copyright 1986-2022 Xilinx, Inc. All Rights Reserved.
** Copyright 2022-2024 Advanced Micro Devices, Inc. All Rights Reserved.
INFO: hw_server application started
INFO: Use Ctrl-C to exit hw_server application
INFO: To connect to this hw_server instance use url: TCP:127.0.0.1:3121
{ XFL12E5XJHWLA}
$ export V80_SERIAL_NUMBER=XFL12E5XJHWL
$ ./target/release/examples/hpu_bench --integer-w 64 --integer-w 32 --iop MUL --iter 10
$ ./target/release/examples/hpu_hlapi
注意:当".hpu"文件没有正确获取时发生的错误可能有点神秘:
memory allocation of ... bytes failed
如果遇到此问题,应运行以下命令:make pull_hpu_files
注意:tfhe-hpu-backend此时只能使用一个V80板,但是如果你的服务器上有多个板,则可以执行
$ . setup_hpu.sh --config v80 -p getopt: option requires an argument -- 'p' Please select a device in following list (1st two digits): 24:00.1 Processing accelerators: Xilinx Corporation Device 50b5 61:00.1 Processing accelerators: Xilinx Corporation Device 50b5 $ . setup_hpu.sh --config v80 -p 61 $ source /opt/xilinx/Vivado/2024.2/settings64.sh # 如果加载了AMI驱动程序并且运行的AMC版本是预期的版本 $ cat /sys/module/ami/drivers/pci\:ami/0000\:61\:00.0/board_serial XFL1UKRD42KW # 列出USB JTAG上可用的序列号 $ xsdb -eval "connect;puts [lsort -unique [regex -all -inline {( XFL[A-Z0-9]*)} [targets -target-properties]]]" ... { XFL12E5XJHWLA} { XFL1UKRD42KWA} $ export V80_SERIAL_NUMBER=XFL1UKRD42KW $ ./target/release/examples/hpu_hlapi
注意:默认情况下,setup_hpu.sh会将AMI_PATH设置为类似/opt/v80/ami/e55d02d的路径,其中e55d02d是AMI驱动程序的git修订版。 为了正确运行,你需要将来自此修订版的已编译的ami.ko放在此目录中,或者将AMI_PATH设置为你的AVED提取:
export AMI_PATH=/home/user/AVED/sw/AMI/driver/
tfhe-rs中还支持一组测试。测试在各种整数宽度的测试包中收集。 这些测试有5个子类:
alu
:运行并检查所有ct x ct IOpalus
:运行并检查所有ct x 标量IOpbitwise
:运行并检查所有按位IOpcmp
:运行并检查所有比较IOpternary
:运行并检查三元运算algo
: 运行并检查专用于卸载小型算法的IOp以下代码段给出了一些可用于测试的命令示例:
## 使用 setup_hpu.sh 脚本正确设置环境
source setup_hpu.sh --config v80 -p
## 运行64b整数宽度的所有子类
cargo test --release --features="hpu-v80" --test hpu -- u64
## 仅运行所有整数宽度IOp 的 `bitwise` 子类
cargo test --release --features="hpu-v80" --test hpu -- bitwise
HPU完全集成在tfhe基准测试系统中。性能结果可以从HighLevelApi或Integer Api中提取。 可以通过以下Makefile目标启动三个基准测试,以简化操作:
## 不要忘记事先正确设置环境
source setup_hpu.sh --config v80 -p
## 运行 hlapi 基准测试
make test_high_level_api_hpu
## 运行 hlapi erc20 基准测试
make bench_hlapi_erc20_hpu
## 运行整数级别基准测试
make bench_integer_hpu
你是否仍在等待你的FPGA板,并对交货时间感到沮丧? 不用担心,你有备份。tfhe-rs中提供了一个专用的仿真基础设施,具有准确的性能估算。 你可以在任何linux/MacOs上使用它来测试HPU在tfhe-rs中的集成,并为HPU目标优化你的应用程序。 只需看一眼 Hpu mockup,然后按照说明操作即可。
- 原文链接: github.com/zama-ai/tfhe-...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!