Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
50352aa
Added .gitignore
DimitrisPapac Apr 8, 2024
78c80af
Added code for TF Lite's reference implementation
DimitrisPapac Apr 8, 2024
8618e14
Attempted to fix trait bounds
DimitrisPapac Apr 9, 2024
c5fea81
Attempted to fix trait bounds.
DimitrisPapac Apr 9, 2024
e8f6154
Collaboratively fixed the code
DimitrisPapac Apr 10, 2024
9082cf7
Fixed bug in frexp
DimitrisPapac Apr 10, 2024
c35f20a
Merge branch 'cesar/speed-up-compatibility' into dimitris/requantize-…
Cesar199999 Apr 10, 2024
661c03a
Add use_requantise_ref flag in model builders
Cesar199999 Apr 10, 2024
93ad93e
Made fixes to quantize_multiplier. Added tests for frexp and quantize…
DimitrisPapac Apr 10, 2024
e38cf8a
Small code changes
DimitrisPapac Apr 11, 2024
73aba23
Add requantisation reference compatibility tests
Cesar199999 Apr 11, 2024
30b0e12
removed hardcoded types QScaleType, etc.; cannot make BMM requantisat…
DimitrisPapac Apr 11, 2024
39b9f70
Revert "removed hardcoded types QScaleType, etc.; cannot make BMM req…
DimitrisPapac Apr 11, 2024
15929af
Fixed tests and replaced panics with asserts.
DimitrisPapac Apr 11, 2024
0b0d55d
Refactored model constructors
DimitrisPapac Apr 11, 2024
c564446
Adapted compatibility tests
DimitrisPapac Apr 11, 2024
5aaee3f
Fixed tests for BMMRef implementation
DimitrisPapac Apr 11, 2024
a6294e4
Introduced pow2_double in order to fix overflow in requantise_ref.
DimitrisPapac Apr 12, 2024
4428263
Debugged requantise_ref
DimitrisPapac Apr 12, 2024
19234a9
Renamed RequantiseBMMNode to RequantiseBMMFloatNode
DimitrisPapac Apr 12, 2024
c92a115
Increased NB_OUTPUTS to 10000.
DimitrisPapac Apr 14, 2024
a71b5e1
Completed initial draft of simplified requantization
DimitrisPapac Apr 15, 2024
c707f44
Updated simple and two-layer perceptron code for simplified requantiz…
DimitrisPapac Apr 15, 2024
467829c
Collaboratively debugged BMM simplified implementation
DimitrisPapac Apr 15, 2024
fc267a6
Updated compatibility tests for the simplified requantization
DimitrisPapac Apr 15, 2024
6d7bb9e
Added tests and partially fixed simplified requantization.
DimitrisPapac Apr 16, 2024
1b99e6e
pinned dependencies as in main
Antonio95 Apr 16, 2024
de213f5
added missing parameter in old function call
Antonio95 Apr 16, 2024
cfe5115
tweaked names and comments
Antonio95 Apr 17, 2024
238e1d5
fixed simplified requantisation
Antonio95 Apr 17, 2024
7d5eb68
both single-round requantisation tests passing, 0 discrepancies
Antonio95 Apr 17, 2024
9b0d9ed
removed unnecessary code
Antonio95 Apr 17, 2024
0d10d9e
changed all requantis... to requantiz... for consistency
Antonio95 Apr 17, 2024
f2f41cc
renamed simplified requantisation to single
Antonio95 Apr 17, 2024
366c4ea
minor renames
Antonio95 Apr 17, 2024
6c685ef
refactored Qarray to Tensor
Antonio95 Apr 18, 2024
a8a1307
refactored InnerType to Numeric
Antonio95 Apr 18, 2024
5ad7abc
realised InnerType doesn't need to be split, renamed to Integral
Antonio95 Apr 18, 2024
1bc3d7f
tmp
Antonio95 Apr 18, 2024
06e29d2
fixed shift direction mix-up
Antonio95 Apr 18, 2024
a83c4ec
finished polishing trait and updating requantisation methods
Antonio95 Apr 18, 2024
fb5f32a
switched implementation of Integral to a macro
Antonio95 Apr 18, 2024
10e7acd
removed unecessary (for now) commented-out code
Antonio95 Apr 18, 2024
8718a79
applied consistent use of parentheses
Antonio95 Apr 19, 2024
d218dc6
Merge pull request #70 from HungryCatsStudio/antonio/refactor_qarray_…
Antonio95 Apr 22, 2024
d73633a
Replace QTypeArray by NIOTensor
Cesar199999 Apr 22, 2024
3f0c62e
Merge branch 'dimitris/requantize-bmm-ref' into cesar/bind-small-and-…
Cesar199999 Apr 22, 2024
0b94605
Use Into and TryInto instead of From and TryFrom
Cesar199999 Apr 23, 2024
96c004d
Update common/src/quantization/mod.rs
Cesar199999 Apr 24, 2024
2ac39f6
Update common/src/model/tensor/mod.rs
Cesar199999 Apr 24, 2024
a9070de
Merge pull request #73 from HungryCatsStudio/cesar/bind-small-and-lar…
Antonio95 Apr 24, 2024
07ef113
Addressed part of the comments that were suggested.
DimitrisPapac Apr 30, 2024
b05c109
Updated strings in requantize_bmm_ref.rs and requantize_bmm_single.rs
DimitrisPapac Apr 30, 2024
f63a5a1
Updated one of the tests in order to match the particular error message.
DimitrisPapac Apr 30, 2024
50fa7b9
minor str fixes
Antonio95 May 2, 2024
f91ecad
introduced convenience function to create suitable requantisation var…
Antonio95 May 3, 2024
3b8ac19
minor message tweak
Antonio95 May 3, 2024
a22823c
Add non-conflicting merge files
Cesar199999 May 3, 2024
e69ea18
Remove duplicated dependency
Cesar199999 May 3, 2024
454f18d
Resolve merge conflicts
Cesar199999 May 3, 2024
6d60ac4
Rename missing instances of QArray to Tensor
Cesar199999 May 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ where `<example_name>` is one of the following:

In order to run any tests involving python code, such as compatibility tests with TF Lite, the feature `python` must be activated (which automatically enables `test-types`).

## From `ndarray` to `QArray`
## From `ndarray` to `Tensor`

In order to save a `numpy` `ndarray` (python side) as a serialised JSON which can be directly read into a `QArray` of ours (Rust side),
In order to save a `numpy` `ndarray` (python side) as a serialised JSON which can be directly read into a `Tensor` of ours (Rust side),
- Convert the `ndarray` into an `OrderedDict` using our custom python function `tensor_to_dict` (available in several of the python notebooks)
- Pass the resulting `OrderedDict` together with the destination path to `json.dump`.

The saved JSON file can be deserialised over in Rust with `QArray::read(path: &str) -> QArray`. If instead of a single `OrderedDict`, a python list of `OrderedDict`s is passed to `json.dump`, the resulting file can be deserialised with `QArray::read_list(path: &str) -> Vec<QArray> `.
The saved JSON file can be deserialised over in Rust with `Tensor::read(path: &str) -> Tensor`. If instead of a single `OrderedDict`, a python list of `OrderedDict`s is passed to `json.dump`, the resulting file can be deserialised with `Tensor::read_list(path: &str) -> Vec<Tensor> `.

Cf. `exploring_tf_lite/training_two_layer_perceptron.ipynb` for example usage.

Expand Down
42 changes: 21 additions & 21 deletions common/examples/common/lib.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
use hcs_common::{quantise_f32_u8_nne, Model, Poly, QArray};
use hcs_common::{quantise_f32_u8_nne, Model, Poly, Tensor};

use ark_crypto_primitives::sponge::{Absorb, CryptographicSponge};
use ark_ff::PrimeField;
use ark_poly_commit::PolynomialCommitment;

// Auxiliary function
fn unpadded_inference<F, S, PCS>(
raw_input: QArray<f32>,
model: &Model<i8, i32>,
raw_input: Tensor<f32>,
model: &Model<i8>,
qinfo: (f32, u8),
) -> QArray<u8>
) -> Tensor<u8>
where
F: PrimeField + Absorb,
S: CryptographicSponge,
PCS: PolynomialCommitment<F, Poly<F>, S>,
{
let quantised_input: QArray<u8> = QArray::new(
let quantised_input: Tensor<u8> = Tensor::new(
quantise_f32_u8_nne(raw_input.values(), qinfo.0, qinfo.1),
raw_input.shape().clone(),
);
Expand All @@ -31,40 +31,40 @@ where
// If padded inference is left on the prover side, move this to the prover
/* // Auxiliary function
fn padded_inference<F, S, PCS>(
raw_input: QArray<f32>,
model: &Model<i8, i32>,
raw_input: Tensor<f32>,
model: &Model<i8>,
qinfo: (f32, u8),
) -> QArray<u8>
) -> Tensor<u8>
where
F: PrimeField + Absorb,
S: CryptographicSponge,
PCS: PolynomialCommitment<F, Poly<F>, S>,
{
let quantised_input: QArray<u8> = QArray::new(
let quantised_input: Tensor<u8> = Tensor::new(
quantise_f32_u8_nne(raw_input.values(), qinfo.0, qinfo.1),
raw_input.shape().clone(),
);

let input_i8 = (quantised_input.cast::<i32>() - 128).cast::<i8>();

let output_i8 =
<Model<i8, i32> as ProveModel<F, S, PCS, i8, i32>>::padded_evaluate(model, input_i8);
<Model<i8> as ProveModel<F, S, PCS, i8>>::padded_evaluate(model, input_i8);

(output_i8.cast::<i32>() + 128).cast()
} */

pub fn run_unpadded<F, S, PCS>(
input_path: &str,
expected_output_path: &str,
model: &Model<i8, i32>,
model: &Model<i8>,
qinfo: (f32, u8),
) where
F: PrimeField + Absorb,
S: CryptographicSponge,
PCS: PolynomialCommitment<F, Poly<F>, S>,
{
let raw_input: QArray<f32> = QArray::read(input_path);
let expected_output: QArray<u8> = QArray::read(expected_output_path);
let raw_input: Tensor<f32> = Tensor::read(input_path);
let expected_output: Tensor<u8> = Tensor::read(expected_output_path);

let output_u8 = unpadded_inference::<F, S, PCS>(raw_input, model, qinfo);

Expand All @@ -78,15 +78,15 @@ pub fn run_unpadded<F, S, PCS>(
/* pub fn run_padded<F, S, PCS>(
input_path: &str,
expected_output_path: &str,
model: &Model<i8, i32>,
model: &Model<i8>,
qinfo: (f32, u8),
) where
F: PrimeField + Absorb,
S: CryptographicSponge,
PCS: PolynomialCommitment<F, Poly<F>, S>,
{
let raw_input: QArray<f32> = QArray::read(input_path);
let expected_output: QArray<u8> = QArray::read(expected_output_path);
let raw_input: Tensor<f32> = Tensor::read(input_path);
let expected_output: Tensor<u8> = Tensor::read(expected_output_path);

let output_u8 = padded_inference::<F, S, PCS>(raw_input, model, qinfo);

Expand All @@ -98,15 +98,15 @@ pub fn run_unpadded<F, S, PCS>(
pub fn multi_run_unpadded<F, S, PCS>(
inputs_path: &str,
expected_outputs_path: &str,
model: &Model<i8, i32>,
model: &Model<i8>,
qinfo: (f32, u8),
) where
F: PrimeField + Absorb,
S: CryptographicSponge,
PCS: PolynomialCommitment<F, Poly<F>, S>,
{
let raw_inputs: Vec<QArray<f32>> = QArray::read_list(inputs_path);
let expected_outputs: Vec<QArray<u8>> = QArray::read_list(expected_outputs_path);
let raw_inputs: Vec<Tensor<f32>> = Tensor::read_list(inputs_path);
let expected_outputs: Vec<Tensor<u8>> = Tensor::read_list(expected_outputs_path);

for (raw_input, expected_output) in raw_inputs.into_iter().zip(expected_outputs.into_iter()) {
assert_eq!(
Expand All @@ -131,8 +131,8 @@ pub fn multi_run_padded<F, S, PCS>(
S: CryptographicSponge,
PCS: PolynomialCommitment<F, Poly<F>, S>,
{
let raw_inputs: Vec<QArray<f32>> = QArray::read_list(inputs_path);
let expected_outputs: Vec<QArray<u8>> = QArray::read_list(expected_outputs_path);
let raw_inputs: Vec<Tensor<f32>> = Tensor::read_list(inputs_path);
let expected_outputs: Vec<Tensor<u8>> = Tensor::read_list(expected_outputs_path);

for (raw_input, expected_output) in raw_inputs.into_iter().zip(expected_outputs.into_iter()) {
assert_eq!(
Expand Down
6 changes: 4 additions & 2 deletions common/examples/simple_perceptron_mnist/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use hcs_common::{
simple_perceptron_mnist::{build_simple_perceptron_mnist, parameters::*},
Ligero,
BMMRequantizationStrategy, Ligero,
};

use ark_bn254::Fr;
Expand All @@ -20,7 +20,9 @@ macro_rules! PATH {
}

fn main() {
let simple_perceptron = build_simple_perceptron_mnist::<Fr, PoseidonSponge<Fr>, Ligero<Fr>>();
let simple_perceptron = build_simple_perceptron_mnist::<Fr, PoseidonSponge<Fr>, Ligero<Fr>>(
BMMRequantizationStrategy::Floating,
);

// Right now this can't be QInfo because the latter is always a pair
// (f32, i8), which indeed matches in-model quantisation, but not
Expand Down
7 changes: 4 additions & 3 deletions common/examples/two_layer_perceptron_mnist/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use hcs_common::{
two_layer_perceptron_mnist::{build_two_layer_perceptron_mnist, parameters::*},
Ligero,
BMMRequantizationStrategy, Ligero,
};

use ark_bn254::Fr;
Expand All @@ -20,8 +20,9 @@ macro_rules! PATH {
}

fn main() {
let two_layer_perceptron =
build_two_layer_perceptron_mnist::<Fr, PoseidonSponge<Fr>, Ligero<Fr>>();
let two_layer_perceptron = build_two_layer_perceptron_mnist::<Fr, PoseidonSponge<Fr>, Ligero<Fr>>(
BMMRequantizationStrategy::Floating,
);

// Right now this can't be QInfo because the latter is always a pair
// (f32, i8), which indeed matches in-model quantisation, but not
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::{BMMNode, Model, Node, Poly, QArray, RequantiseBMMNode, ReshapeNode};
use crate::{
quantization::BMMRequantizationStrategy, utils::req_bmm_from_strategy, BMMNode, Model, Node,
Poly, ReshapeNode, Tensor,
};

use ark_crypto_primitives::sponge::{Absorb, CryptographicSponge};
use ark_ff::PrimeField;
Expand All @@ -22,7 +25,9 @@ macro_rules! PATH {
}

// TODO this is incorrect now that we have switched to logs
pub fn build_simple_perceptron_mnist<F, S, PCS>() -> Model<i8, i32>
pub fn build_simple_perceptron_mnist<F, S, PCS>(
req_strategy: BMMRequantizationStrategy,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this means there's one requantization strategy per model, yes?
Is this what we want?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, basically these are just auxiliary functions that construct our example models by filling the vector of nodes with the nodes it should have. In one of the models there is only one requantisation node and in the other one there are two. In the latter case, we don't have any need to mix requantisation strategies (which sounds a bit unlikely anyway). Still, it should be stressed that this is just code to build examples, not library functionality code. Therefore the "lack of generality" shouldn't be a problem.

Incidentally, the reason we added this argument was so that we could test out how much the reference, single-round and floating-point-based implementations of requantisation each differ from TF Lite execution.

) -> Model<i8>
where
F: PrimeField + Absorb,
S: CryptographicSponge,
Expand All @@ -32,20 +37,15 @@ where

let reshape: ReshapeNode = ReshapeNode::new(INPUT_DIMS.to_vec(), vec![flat_dim]);

let w_array: QArray<i8> = QArray::read(&format!(PATH!(), "weights.json"));
let b_array: QArray<i32> = QArray::read(&format!(PATH!(), "bias.json"));
let w_array: Tensor<i8> = Tensor::read(&format!(PATH!(), "weights.json"));
let b_array: Tensor<i32> = Tensor::read(&format!(PATH!(), "bias.json"));

let bmm: BMMNode<i8, i32> = BMMNode::new(w_array, b_array, Z_I);
let bmm: BMMNode<i8> = BMMNode::new(w_array, b_array, Z_I);

let req_bmm: RequantiseBMMNode<i8> =
RequantiseBMMNode::new(OUTPUT_DIM, S_I, Z_I, S_W, Z_W, S_O, Z_O);
let req_bmm = req_bmm_from_strategy(req_strategy, OUTPUT_DIM, S_I, Z_I, S_W, Z_W, S_O, Z_O);

Model::new(
INPUT_DIMS.to_vec(),
vec![
Node::Reshape(reshape),
Node::BMM(bmm),
Node::RequantiseBMM(req_bmm),
],
vec![Node::Reshape(reshape), Node::BMM(bmm), req_bmm],
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use ark_poly_commit::PolynomialCommitment;
pub mod parameters;
use parameters::*;

use crate::{BMMNode, Model, Node, Poly, QArray, ReLUNode, RequantiseBMMNode, ReshapeNode};
use crate::{
quantization::BMMRequantizationStrategy, utils::req_bmm_from_strategy, BMMNode, Model, Node,
Poly, ReLUNode, ReshapeNode, Tensor,
};

pub const INPUT_DIMS: &[usize] = &[28, 28];
pub const INTER_DIM: usize = 28;
Expand All @@ -22,7 +25,9 @@ macro_rules! PATH {
};
}

pub fn build_two_layer_perceptron_mnist<F, S, PCS>() -> Model<i8, i32>
pub fn build_two_layer_perceptron_mnist<F, S, PCS>(
req_strategy: BMMRequantizationStrategy,
) -> Model<i8>
where
F: PrimeField + Absorb,
S: CryptographicSponge,
Expand All @@ -32,32 +37,48 @@ where

let reshape: ReshapeNode = ReshapeNode::new(INPUT_DIMS.to_vec(), vec![flat_dim]);

let w1_array: QArray<i8> = QArray::read(&format!(PATH!(), "weights_1.json"));
let b1_array: QArray<i32> = QArray::read(&format!(PATH!(), "bias_1.json"));
let w2_array: QArray<i8> = QArray::read(&format!(PATH!(), "weights_2.json"));
let b2_array: QArray<i32> = QArray::read(&format!(PATH!(), "bias_2.json"));
let w1_array: Tensor<i8> = Tensor::read(&format!(PATH!(), "weights_1.json"));
let b1_array: Tensor<i32> = Tensor::read(&format!(PATH!(), "bias_1.json"));
let w2_array: Tensor<i8> = Tensor::read(&format!(PATH!(), "weights_2.json"));
let b2_array: Tensor<i32> = Tensor::read(&format!(PATH!(), "bias_2.json"));

let bmm_1: BMMNode<i8, i32> = BMMNode::new(w1_array, b1_array, Z_1_I);
let bmm_1: BMMNode<i8> = BMMNode::new(w1_array, b1_array, Z_1_I);

let req_bmm_1: RequantiseBMMNode<i8> =
RequantiseBMMNode::new(INTER_DIM, S_1_I, Z_1_I, S_1_W, Z_1_W, S_1_O, Z_1_O);
let req_bmm_1 = req_bmm_from_strategy(
req_strategy,
INTER_DIM,
S_1_I,
Z_1_I,
S_1_W,
Z_1_W,
S_1_O,
Z_1_O,
);

let relu: ReLUNode<i8> = ReLUNode::new(28, Z_1_O);

let bmm_2: BMMNode<i8, i32> = BMMNode::new(w2_array, b2_array, Z_2_I);
let bmm_2: BMMNode<i8> = BMMNode::new(w2_array, b2_array, Z_2_I);

let req_bmm_2: RequantiseBMMNode<i8> =
RequantiseBMMNode::new(OUTPUT_DIM, S_2_I, Z_2_I, S_2_W, Z_2_W, S_2_O, Z_2_O);
let req_bmm_2 = req_bmm_from_strategy(
req_strategy,
OUTPUT_DIM,
S_2_I,
Z_2_I,
S_2_W,
Z_2_W,
S_2_O,
Z_2_O,
);

Model::new(
INPUT_DIMS.to_vec(),
vec![
Node::Reshape(reshape),
Node::BMM(bmm_1),
Node::RequantiseBMM(req_bmm_1),
req_bmm_1,
Node::ReLU(relu),
Node::BMM(bmm_2),
Node::RequantiseBMM(req_bmm_2),
req_bmm_2,
],
)
}
14 changes: 7 additions & 7 deletions common/src/compatibility/python/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{fs::create_dir_all, path::Path};

use pyo3::{prelude::*, PyAny};

use crate::QArray;
use crate::Tensor;

const PERCEPTRON_PATH: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
Expand All @@ -21,7 +21,7 @@ pub fn get_model(py: Python, model_name: &str, args: Option<Vec<(&str, &str)>>)
func.call1(py, (model_name, args)).unwrap()
}

pub fn save_model_parameters_as_qarray(py: Python, model: &Py<PyAny>, path: &str) {
pub fn save_model_parameters_as_tensor(py: Python, model: &Py<PyAny>, path: &str) {
let path = Path::new(path);

if !path.exists() {
Expand All @@ -30,13 +30,13 @@ pub fn save_model_parameters_as_qarray(py: Python, model: &Py<PyAny>, path: &str
}

model
.call_method1(py, "save_params_as_qarray", (path,))
.call_method1(py, "save_params_as_verifiaml_tensor", (path,))
.unwrap();
}

pub fn get_model_input<'py, T>(python: Python<'py>, model: &Py<PyAny>, index: usize) -> QArray<f32>
pub fn get_model_input<'py, T>(python: Python<'py>, model: &Py<PyAny>, index: usize) -> Tensor<f32>
where
T: Into<QArray<f32>> + FromPyObject<'py> + Clone,
T: Into<Tensor<f32>> + FromPyObject<'py> + Clone,
{
let result = model.call_method1(python, "get_input", (index,));

Expand All @@ -46,10 +46,10 @@ where
model_input.into()
}

pub fn get_model_output(py: Python, model: &Py<PyAny>, index: usize) -> QArray<u8> {
pub fn get_model_output(py: Python, model: &Py<PyAny>, index: usize) -> Tensor<u8> {
let result = model.call_method1(py, "get_output", (index,));
// Downcast the result to the expected type
let model_output = result.unwrap().extract::<Vec<u8>>(py).unwrap();

QArray::from(model_output)
Tensor::from(model_output)
}
Loading