10 KiB
HPU acceleration
This guide explains how to update your existing program to leverage HPU acceleration, or to start a new program using HPU.
TFHE-rs now supports a HPU backend based on FPGA implementation, enabling integer arithmetic operations on encrypted data.
Prerequisites
- An AMD/Xilinx V80 board installed on a server running Linux with kernel 5.15.0-*
- A HPU bitstream that you can find (or build) in HPU fpga repository and load in V80 flash and FPGA using its README
- AMI linux device driver version from this fork
- QDMA linux device driver version from this fork
- Rust version - check this page
Importing to your project
To use the TFHE-rs HPU backend in your project, add the following dependency in your Cargo.toml.
tfhe = { version = "~1.4.1", features = ["integer", "hpu-v80"] }
{% hint style="success" %}
For optimal performance when using TFHE-rs, run your code in release mode with the --release flag.
{% endhint %}
Supported platforms
TFHE-rs HPU backend is only supported on Linux (x86).
| OS | x86 | aarch64 |
|---|---|---|
| Linux | Supported | Unsupported |
| macOS | Unsupported | Unsupported |
| Windows | Unsupported | Unsupported |
A first example
Configuring and creating keys.
Comparing to the CPU example, HPU set up differs in the key creation and device registration, as detailed here
Here is a full example (combining the client and server parts):
use tfhe::{Config, set_server_key, FheUint8, ClientKey, CompressedServerKey};
use tfhe::prelude::*;
use tfhe::tfhe_hpu_backend::prelude::*;
fn main() {
// Instantiate HpuDevice --------------------------------------------------
// HPU configuration knobs are retrieved from a TOML configuration file. Prebuilt configurations could be find in `backends/tfhe-hpu-backend/config_store`
// For ease of use a setup_hpu.sh script is available in repository root folder and it handle the required environment variables setup and driver initialisation
// More details are available in `backends/tfhe-hpu-backend/README.md`
let hpu_device = HpuDevice::from_config(ShellString::new("${HPU_BACKEND_DIR}/config_store/${HPU_CONFIG}/hpu_config.toml".to_string()).expand().as_str());
// Generate keys ----------------------------------------------------------
let config = Config::from_hpu_device(&hpu_device);
let client_key = ClientKey::generate(config);
let compressed_server_key = CompressedServerKey::new(&client_key);
// Register HpuDevice and key as thread-local engine
set_server_key((hpu_device, compressed_server_key));
let clear_a = 27u8;
let clear_b = 128u8;
let a = FheUint8::encrypt(clear_a, &client_key);
let b = FheUint8::encrypt(clear_b, &client_key);
// Server-side computation
let result = a + b;
// Client-side
let decrypted_result: u8 = result.decrypt(&client_key);
let clear_result = clear_a + clear_b;
assert_eq!(decrypted_result, clear_result);
}
Setting the hpu
An HPU device is built for a given parameter set. At this point, because HPU is still a prototype, the software provided is retrieving this parameter set from an instantiated HpuDevice. Once retrieved, reading some HPU registers, this parameter set is used by the example applications to generate both client and compressed server keys. Server key has then to be decompressed by the server to be converted into the right format and uploaded to the device. Once decompressed, the operations between CPU and HPU are identical.
Encryption
On the client-side, the method to encrypt the data is exactly the same than the CPU one, as shown in the following example:
let clear_a = 27u8;
let clear_b = 128u8;
let a = FheUint8::encrypt(clear_a, &client_key);
let b = FheUint8::encrypt(clear_b, &client_key);
Computation
The server first needs to set up its keys with set_server_key((hpu_device, compressed_server_key)).
Then, homomorphic computations are performed using the same approach as the CPU operations.
// Server-side
let result = a + b;
//Client-side
let decrypted_result: u8 = result.decrypt(&client_key);
let clear_result = clear_a + clear_b;
assert_eq!(decrypted_result, clear_result);
Decryption
Finally, the client decrypts the result using:
let decrypted_result: u8 = result.decrypt(&client_key);
List of available operations
The HPU backend includes the following operations for unsigned encrypted integers:
| name | symbol | Enc/Enc |
Enc/ Int |
|---|---|---|---|
| Add | + |
✔️ | ✔️ |
| Sub | - |
✔️ | ✔️ |
| Mul | * |
✔️ | ✔️ |
| Div | / |
✔️ | ✔️ |
| Rem | % |
✔️ | ✔️ |
| BitAnd | & |
✔️ | ✔️ |
| BitOr | | |
✔️ | ✔️ |
| BitXor | ^ |
✔️ | ✔️ |
| Shr | >> |
✔️ | ✔️ |
| Shl | << |
✔️ | ✔️ |
| Rotate right | rotate_right |
✔️ | ✔️ |
| Rotate left | rotate_left |
✔️ | ✔️ |
| Min | min |
✔️ | ✔️ |
| Max | max |
✔️ | ✔️ |
| Greater than | gt |
✔️ | ✔️ |
| Greater or equal than | ge |
✔️ | ✔️ |
| Lower than | lt |
✔️ | ✔️ |
| Lower or equal than | le |
✔️ | ✔️ |
| Equal | eq |
✔️ | ✔️ |
| Not Equal | ne |
✔️ | ✔️ |
| Ternary operator | select |
✔️ | ✖️ |
| Integer logarithm | ilog2 |
✔️ | N/A |
| Count trailing/leading ones | leading_zeros |
✔️ | N/A |
| Count trailing/leading zeros | leading_ones |
✔️ | N/A |
{% hint style="info" %} All operations follow the same syntax than the one described in here. {% endhint %}