From f6f8d8a32c0bffee2c82c7b5116d2f5f798b30d4 Mon Sep 17 00:00:00 2001 From: Richa Jain Date: Mon, 8 Jul 2024 08:04:15 +0530 Subject: [PATCH 01/11] Neural Network --- .../src/protocol/ipa_prf/boolean_ops/mod.rs | 1 + .../ipa_prf/boolean_ops/multiplication.rs | 17 ++-- .../protocol/ipa_prf/boolean_ops/sigmoid.rs | 93 +++++++++++++++++++ 3 files changed, 100 insertions(+), 11 deletions(-) diff --git a/ipa-core/src/protocol/ipa_prf/boolean_ops/mod.rs b/ipa-core/src/protocol/ipa_prf/boolean_ops/mod.rs index a50c260ad..ed1aa04d0 100644 --- a/ipa-core/src/protocol/ipa_prf/boolean_ops/mod.rs +++ b/ipa-core/src/protocol/ipa_prf/boolean_ops/mod.rs @@ -5,4 +5,5 @@ pub(crate) mod step; pub use share_conversion_aby::{ convert_to_fp25519, expand_shared_array_in_place, extract_from_shared_array, }; +pub(crate) mod multiplication; pub mod sigmoid; diff --git a/ipa-core/src/protocol/ipa_prf/boolean_ops/multiplication.rs b/ipa-core/src/protocol/ipa_prf/boolean_ops/multiplication.rs index 345747043..64d222ce5 100644 --- a/ipa-core/src/protocol/ipa_prf/boolean_ops/multiplication.rs +++ b/ipa-core/src/protocol/ipa_prf/boolean_ops/multiplication.rs @@ -1,28 +1,22 @@ -use std::iter::zip; +use ipa_step::StepNarrow; use crate::{ error::Error, ff::boolean::Boolean, protocol::{ - context::Context, - RecordId, - ipa_prf::boolean_ops::{ - addition_sequential::integer_add, - }, + basics::{BooleanProtocols}, + boolean::NBitStep, + context::Context, RecordId, }, secret_sharing::{replicated::semi_honest::AdditiveShare, BitDecomposed, FieldSimd}, }; - pub async fn integer_mul( ctx: C, record_id: RecordId, x: &BitDecomposed>, y: &BitDecomposed>, -) -> Result< - BitDecomposed>, - Error, -> +) -> Result>, Error> where C: Context, S: NBitStep, @@ -30,4 +24,5 @@ where AdditiveShare: BooleanProtocols, { //TODO: To be implemented + Err(Error::Unsupported("still not implemented".to_owned())) } diff --git a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs index 5fc7eb5c6..48ccd7537 100644 --- a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs +++ b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs @@ -12,6 +12,8 @@ use crate::{ secret_sharing::{replicated::semi_honest::AdditiveShare, BitDecomposed, FieldSimd}, }; +use super::multiplication::integer_mul; + async fn a_times_b_and_not_b( ctx: &C, record_id: RecordId, @@ -158,6 +160,97 @@ where ])) } +// Sigmoid( +// Sum(i = 1..N, neuron(i) in last layer activation times edge weight connecting that neuron to this) +// ) +// + +// for i in 0..M-1 // For going through all layers +// for j in 0..N-1 // Current layer +// for k in 0..N-1 // For previous layer +// neuron(i*N + j) += neuron((i-1)*N + k) * edge_weight(neuron((i)*N + j), neuron((i-1)*N + k)) + +// M neurons wide, L layers tall +pub async fn neural_network( + ctx: C, + first_layer: &[BitDecomposed>; M], + edge_weights: &[&[&[BitDecomposed>; M]; M]; L], +) -> Result<[BitDecomposed>; M], Error> +where + C: Context, + Boolean: FieldSimd, + AdditiveShare: BooleanProtocols, + Boolean: FieldSimd, + AdditiveShare: BooleanProtocols, + Boolean: FieldSimd, + AdditiveShare: BooleanProtocols, +{ + Err(Error::Unsupported("still not implemented".to_owned())) + let mut last_layer = first_layer; + // for each layer we get M*M vector of edge_weights + (0..L).map(|k| { + let mut this_layer : [BitDecomposed>; M]; + (0..M).map(|j| { + let this_neuron = 0; + (0..M).map(|i| { + // this_neuron += mult(edge_weights(j,i), last_layer(i)) + let this_neuron = integer_mul( + ctx.set_total_records(M), + RecordId::from(j), + &edge_weights[k][j][i], + &last_layer[i], + ) + .await + .unwrap(); + // TODO truncate this neuron to -256 to +256 + //this_layer[j] = sigmoid(this_neuron) + this_layer[j] = sigmoid::<_, N>( + ctx.set_total_records(N), + RecordId::from(j), + this_neuron, + ) + .await + .unwrap(); + }).collect::<_>() + }).collect::<_>(); + last_layer = &this_layer; + }).collect::<_>(); + Ok(last_layer) +} + +// let last_layer = first_layer; +// // for each layer we get M*M vector of edge_weights +// for k in 0..L { +// let this_layer : [BitDecomposed>; M]; +// for j in 0..M { +// let this_neuron = 0; +// for i in 0..M { +// // this_neuron += mult(edge_weights(j,i), last_layer(i)) +// let result = integer_mul( +// ctx.set_total_records(M), +// RecordId::from(j), +// &get_edge_weights_at(k,j,i), +// &last_layer[i], +// ) +// .await +// .unwrap(); + +// } +// // truncate this neuron to -256 to +256 + +// //this_layer[j] = sigmoid(this_neuron) +// let result = sigmoid::<_, N>( +// ctx.set_total_records(N), +// RecordId::from(j), +// &BitDecomposed::transposed_from(this_neuron).unwrap(), +// ) +// .await +// .unwrap(); + +// } +// last_layer = this_layer; +// } + #[cfg(all(test, unit_test))] mod test { use std::num::TryFromIntError; From fece842857a413e74619b2fe247836d2a6acfa8a Mon Sep 17 00:00:00 2001 From: Richa Jain Date: Mon, 8 Jul 2024 09:39:54 +0530 Subject: [PATCH 02/11] More code --- ipa-core/src/protocol/ipa_prf/boolean_ops/mod.rs | 1 - .../src/protocol/ipa_prf/boolean_ops/sigmoid.rs | 13 ++++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/ipa-core/src/protocol/ipa_prf/boolean_ops/mod.rs b/ipa-core/src/protocol/ipa_prf/boolean_ops/mod.rs index 4c12bdf6e..78f2631e8 100644 --- a/ipa-core/src/protocol/ipa_prf/boolean_ops/mod.rs +++ b/ipa-core/src/protocol/ipa_prf/boolean_ops/mod.rs @@ -6,5 +6,4 @@ pub(crate) mod step; pub use share_conversion_aby::{ convert_to_fp25519, expand_shared_array_in_place, extract_from_shared_array, }; -pub(crate) mod multiplication; pub mod sigmoid; diff --git a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs index 48ccd7537..e7de7f4cb 100644 --- a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs +++ b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs @@ -185,17 +185,16 @@ where Boolean: FieldSimd, AdditiveShare: BooleanProtocols, { - Err(Error::Unsupported("still not implemented".to_owned())) + // Err(Error::Unsupported("still not implemented".to_owned())) let mut last_layer = first_layer; // for each layer we get M*M vector of edge_weights - (0..L).map(|k| { - let mut this_layer : [BitDecomposed>; M]; - (0..M).map(|j| { + (0..L).map(|k| { // for each layer + (0..M).map(|j| { // this can be parallelised - each neuron on a layer let this_neuron = 0; (0..M).map(|i| { // this_neuron += mult(edge_weights(j,i), last_layer(i)) let this_neuron = integer_mul( - ctx.set_total_records(M), + ctx.set_total_records(1), RecordId::from(j), &edge_weights[k][j][i], &last_layer[i], @@ -205,7 +204,7 @@ where // TODO truncate this neuron to -256 to +256 //this_layer[j] = sigmoid(this_neuron) this_layer[j] = sigmoid::<_, N>( - ctx.set_total_records(N), + ctx.set_total_records(1), RecordId::from(j), this_neuron, ) @@ -215,7 +214,7 @@ where }).collect::<_>(); last_layer = &this_layer; }).collect::<_>(); - Ok(last_layer) + Ok(last_layer.clone()) } // let last_layer = first_layer; From 3ca0225814723947fb3eff284b83d510595f6ed4 Mon Sep 17 00:00:00 2001 From: Richa Jain Date: Tue, 9 Jul 2024 15:04:46 +0530 Subject: [PATCH 03/11] Sigmoid added --- .../protocol/ipa_prf/boolean_ops/sigmoid.rs | 166 ++++++++++-------- 1 file changed, 91 insertions(+), 75 deletions(-) diff --git a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs index e7de7f4cb..6d5b51616 100644 --- a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs +++ b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs @@ -1,4 +1,4 @@ -use std::{iter::repeat, ops::Not}; +use std::{iter::{repeat, zip}, ops::Not}; use futures::future::{try_join, try_join4, try_join5}; @@ -6,13 +6,12 @@ use crate::{ error::Error, ff::boolean::Boolean, protocol::{ - basics::mul::SecureMul, boolean::step::ThirtyTwoBitStep, context::Context, + basics::mul::SecureMul, boolean::{step::{ThirtyTwoBitStep, TwoHundredFiftySixBitOpStep}, NBitStep}, context::Context, BooleanProtocols, RecordId, }, secret_sharing::{replicated::semi_honest::AdditiveShare, BitDecomposed, FieldSimd}, }; - -use super::multiplication::integer_mul; +use super::{multiplication::integer_mul, addition_sequential::integer_add}; async fn a_times_b_and_not_b( ctx: &C, @@ -170,98 +169,86 @@ where // for k in 0..N-1 // For previous layer // neuron(i*N + j) += neuron((i-1)*N + k) * edge_weight(neuron((i)*N + j), neuron((i-1)*N + k)) -// M neurons wide, L layers tall -pub async fn neural_network( +// M' neurons wide and here M is M'/N, L layers tall +pub async fn neural_network( ctx: C, - first_layer: &[BitDecomposed>; M], - edge_weights: &[&[&[BitDecomposed>; M]; M]; L], -) -> Result<[BitDecomposed>; M], Error> + last_layer_neurons: &[BitDecomposed>; M], + edge_weights: &[BitDecomposed>; M], +) -> Result>, Error> where C: Context, + S: NBitStep, Boolean: FieldSimd, AdditiveShare: BooleanProtocols, Boolean: FieldSimd, AdditiveShare: BooleanProtocols, - Boolean: FieldSimd, - AdditiveShare: BooleanProtocols, { - // Err(Error::Unsupported("still not implemented".to_owned())) - let mut last_layer = first_layer; + // use super::step::MultiplicationStep as Step; // for each layer we get M*M vector of edge_weights - (0..L).map(|k| { // for each layer - (0..M).map(|j| { // this can be parallelised - each neuron on a layer - let this_neuron = 0; - (0..M).map(|i| { - // this_neuron += mult(edge_weights(j,i), last_layer(i)) - let this_neuron = integer_mul( - ctx.set_total_records(1), - RecordId::from(j), - &edge_weights[k][j][i], - &last_layer[i], - ) - .await - .unwrap(); - // TODO truncate this neuron to -256 to +256 - //this_layer[j] = sigmoid(this_neuron) - this_layer[j] = sigmoid::<_, N>( - ctx.set_total_records(1), - RecordId::from(j), - this_neuron, - ) - .await - .unwrap(); - }).collect::<_>() - }).collect::<_>(); - last_layer = &this_layer; - }).collect::<_>(); - Ok(last_layer.clone()) + let mut mults = ctx.parallel_join(zip(edge_weights.iter(), last_layer_neurons).enumerate().map(|(i, (edge_weight, neuron))| { + let ctx = ctx.narrow(&TwoHundredFiftySixBitOpStep::Bit(i)); + async move { + integer_mul::<_, S, N>( + ctx, + RecordId::FIRST, + &edge_weight, + &neuron, + ) + .await + } + })).await?; + + let mut num = 0; + while mults.len() > 1 { + // Add each of the mults amongst themselves + for (a, b) in mults.iter().tuples() { + let (add_result, _) = integer_add::<_, S, N>( + ctx.narrow(&TwoHundredFiftySixBitOpStep::Bit(M+num)), + RecordId::from(num), + &a, + &b, + ) + .await?; + mults.push(add_result); + num += 1; + } + + } + // now add the last N elements in 1 BitDecomposed + let mut one_cell = mults[0]; + while one_cell.len() > 1 { + let (left, right) = one_cell.split_at((one_cell.len()/2).try_into().unwrap()); + (one_cell, _) = integer_add::<_, S, N>( + ctx.narrow(&TwoHundredFiftySixBitOpStep::Bit(M+num)), + RecordId::FIRST, + &left, + &right, + ) + .await?; + num += 1; + } + sigmoid::<_, N>( + ctx.narrow(&TwoHundredFiftySixBitOpStep::Bit(M+num)), + RecordId::FIRST, + &one_cell, + ) + .await } -// let last_layer = first_layer; -// // for each layer we get M*M vector of edge_weights -// for k in 0..L { -// let this_layer : [BitDecomposed>; M]; -// for j in 0..M { -// let this_neuron = 0; -// for i in 0..M { -// // this_neuron += mult(edge_weights(j,i), last_layer(i)) -// let result = integer_mul( -// ctx.set_total_records(M), -// RecordId::from(j), -// &get_edge_weights_at(k,j,i), -// &last_layer[i], -// ) -// .await -// .unwrap(); - -// } -// // truncate this neuron to -256 to +256 - -// //this_layer[j] = sigmoid(this_neuron) -// let result = sigmoid::<_, N>( -// ctx.set_total_records(N), -// RecordId::from(j), -// &BitDecomposed::transposed_from(this_neuron).unwrap(), -// ) -// .await -// .unwrap(); - -// } -// last_layer = this_layer; -// } - #[cfg(all(test, unit_test))] mod test { use std::num::TryFromIntError; use crate::{ ff::{boolean_array::BA8, U128Conversions}, - protocol::{context::Context, ipa_prf::boolean_ops::sigmoid::sigmoid, RecordId}, - secret_sharing::{BitDecomposed, SharedValue, TransposeFrom}, + protocol::{context::Context, ipa_prf::boolean_ops::sigmoid::sigmoid, RecordId, boolean::step::DefaultBitStep}, + secret_sharing::{BitDecomposed, SharedValue, TransposeFrom, replicated::semi_honest::AdditiveShare}, test_executor::run, test_fixture::{Reconstruct, Runner, TestWorld}, }; + use super::neural_network; + fn piecewise_linear_sigmoid_approximation(x: i128) -> Result { Ok(match x { i128::MIN..=-113 => 0, @@ -329,4 +316,33 @@ mod test { } }); } + + #[test] + #[allow(clippy::cast_precision_loss)] + fn semi_honest_neural_network() { + run(|| async move { + let world = TestWorld::default(); + + let edge_weights = (0..256).map(|i| BA8::truncate_from(u128::try_from(i).unwrap())); + let prev_neurons = (0..256).map(|i| BA8::truncate_from(u128::try_from(i).unwrap())); + let result = world + .upgraded_semi_honest((edge_weights, prev_neurons), |ctx, (edge_weights, prev_neurons)| async move { + let edge_weights1 = BitDecomposed::transposed_from(&edge_weights).unwrap(); + let prev_neurons1 = BitDecomposed::transposed_from(&prev_neurons).unwrap(); + let edge_weights = [edge_weights1.clone(), edge_weights1.clone(), edge_weights1.clone(), edge_weights1.clone(), edge_weights1.clone(), edge_weights1.clone(), edge_weights1.clone(), edge_weights1]; + let prev_neurons = [prev_neurons1.clone(), prev_neurons1.clone(), prev_neurons1.clone(), prev_neurons1.clone(), prev_neurons1.clone(), prev_neurons1.clone(), prev_neurons1.clone(), prev_neurons1]; + let result = neural_network::<_, DefaultBitStep, 8, 256, 2048>( + ctx.set_total_records(1), + &prev_neurons, + &edge_weights + ) + .await + .unwrap(); + + // Vec::transposed_from(&result).unwrap() + }) + .await; + + }); + } } From 9f89d9d09a3e85ae92dd3999eee694542fc2c295 Mon Sep 17 00:00:00 2001 From: Richa Jain Date: Tue, 9 Jul 2024 16:07:35 +0530 Subject: [PATCH 04/11] One layer --- .../protocol/ipa_prf/boolean_ops/sigmoid.rs | 159 ++++++++---------- 1 file changed, 74 insertions(+), 85 deletions(-) diff --git a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs index 6d5b51616..6a2d7f004 100644 --- a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs +++ b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs @@ -1,17 +1,26 @@ -use std::{iter::{repeat, zip}, ops::Not}; - -use futures::future::{try_join, try_join4, try_join5}; +use super::{multiplication::integer_mul}; +use futures::{stream, StreamExt}; use crate::{ error::Error, - ff::boolean::Boolean, + ff::{boolean::Boolean, boolean_array::BA16}, protocol::{ - basics::mul::SecureMul, boolean::{step::{ThirtyTwoBitStep, TwoHundredFiftySixBitOpStep}, NBitStep}, context::Context, + basics::mul::SecureMul, + boolean::{ + step::{ThirtyTwoBitStep}, + NBitStep, + }, + context::Context, + ipa_prf::aggregation::aggregate_values, BooleanProtocols, RecordId, }, secret_sharing::{replicated::semi_honest::AdditiveShare, BitDecomposed, FieldSimd}, }; -use super::{multiplication::integer_mul, addition_sequential::integer_add}; +use futures::future::{try_join, try_join4, try_join5}; +use std::{ + iter::{repeat, zip}, + ops::Not, +}; async fn a_times_b_and_not_b( ctx: &C, @@ -164,90 +173,69 @@ where // ) // -// for i in 0..M-1 // For going through all layers -// for j in 0..N-1 // Current layer -// for k in 0..N-1 // For previous layer -// neuron(i*N + j) += neuron((i-1)*N + k) * edge_weight(neuron((i)*N + j), neuron((i-1)*N + k)) - -// M' neurons wide and here M is M'/N, L layers tall -pub async fn neural_network( +// edge_weights[0] holds all the edge weights from all the neurons from the previous layer to neuron[0] in the current layer +pub async fn one_layer( ctx: C, - last_layer_neurons: &[BitDecomposed>; M], - edge_weights: &[BitDecomposed>; M], -) -> Result>, Error> + last_layer_neurons: &BitDecomposed>, + edge_weights: &[BitDecomposed>; N], +) -> Result>, Error> where C: Context, S: NBitStep, Boolean: FieldSimd, AdditiveShare: BooleanProtocols, - Boolean: FieldSimd, - AdditiveShare: BooleanProtocols, -{ - // use super::step::MultiplicationStep as Step; - // for each layer we get M*M vector of edge_weights - let mut mults = ctx.parallel_join(zip(edge_weights.iter(), last_layer_neurons).enumerate().map(|(i, (edge_weight, neuron))| { - let ctx = ctx.narrow(&TwoHundredFiftySixBitOpStep::Bit(i)); - async move { - integer_mul::<_, S, N>( - ctx, - RecordId::FIRST, - &edge_weight, - &neuron, - ) - .await - } - })).await?; - - let mut num = 0; - while mults.len() > 1 { - // Add each of the mults amongst themselves - for (a, b) in mults.iter().tuples() { - let (add_result, _) = integer_add::<_, S, N>( - ctx.narrow(&TwoHundredFiftySixBitOpStep::Bit(M+num)), - RecordId::from(num), - &a, - &b, +{ + let edge_activations = ctx + .parallel_join( + zip(edge_weights.iter(), repeat(last_layer_neurons)) + .enumerate() + .map(|(i, (edge_weight, neuron))| { + let ctx = ctx.narrow("activation_times_edge_weight"); + async move { + integer_mul::<_, S, N>( + ctx, + RecordId::from(i), + &edge_weight, + &last_layer_neurons, + ) + .await + } + }), ) - .await?; - mults.push(add_result); - num += 1; - } - - } + .await; + // now add the last N elements in 1 BitDecomposed - let mut one_cell = mults[0]; - while one_cell.len() > 1 { - let (left, right) = one_cell.split_at((one_cell.len()/2).try_into().unwrap()); - (one_cell, _) = integer_add::<_, S, N>( - ctx.narrow(&TwoHundredFiftySixBitOpStep::Bit(M+num)), - RecordId::FIRST, - &left, - &right, - ) - .await?; - num += 1; - } + let aggregated_edge_weights = aggregate_values::<_, BA16, N>( + ctx.narrow("aggregated_edge_weights"), + stream::iter(edge_activations).boxed(), + N, + ) + .await?; + sigmoid::<_, N>( - ctx.narrow(&TwoHundredFiftySixBitOpStep::Bit(M+num)), - RecordId::FIRST, - &one_cell, - ) - .await + ctx.narrow("sigmoid"), + RecordId::FIRST, + &aggregated_edge_weights, + ) + .await } #[cfg(all(test, unit_test))] mod test { - use std::num::TryFromIntError; + use std::{num::TryFromIntError, iter::repeat}; use crate::{ ff::{boolean_array::BA8, U128Conversions}, - protocol::{context::Context, ipa_prf::boolean_ops::sigmoid::sigmoid, RecordId, boolean::step::DefaultBitStep}, - secret_sharing::{BitDecomposed, SharedValue, TransposeFrom, replicated::semi_honest::AdditiveShare}, + protocol::{ + boolean::step::DefaultBitStep, context::Context, + ipa_prf::boolean_ops::sigmoid::sigmoid, RecordId, + }, + secret_sharing::{BitDecomposed, SharedValue, TransposeFrom}, test_executor::run, test_fixture::{Reconstruct, Runner, TestWorld}, }; - use super::neural_network; + use super::one_layer; fn piecewise_linear_sigmoid_approximation(x: i128) -> Result { Ok(match x { @@ -326,23 +314,24 @@ mod test { let edge_weights = (0..256).map(|i| BA8::truncate_from(u128::try_from(i).unwrap())); let prev_neurons = (0..256).map(|i| BA8::truncate_from(u128::try_from(i).unwrap())); let result = world - .upgraded_semi_honest((edge_weights, prev_neurons), |ctx, (edge_weights, prev_neurons)| async move { - let edge_weights1 = BitDecomposed::transposed_from(&edge_weights).unwrap(); - let prev_neurons1 = BitDecomposed::transposed_from(&prev_neurons).unwrap(); - let edge_weights = [edge_weights1.clone(), edge_weights1.clone(), edge_weights1.clone(), edge_weights1.clone(), edge_weights1.clone(), edge_weights1.clone(), edge_weights1.clone(), edge_weights1]; - let prev_neurons = [prev_neurons1.clone(), prev_neurons1.clone(), prev_neurons1.clone(), prev_neurons1.clone(), prev_neurons1.clone(), prev_neurons1.clone(), prev_neurons1.clone(), prev_neurons1]; - let result = neural_network::<_, DefaultBitStep, 8, 256, 2048>( - ctx.set_total_records(1), - &prev_neurons, - &edge_weights - ) - .await - .unwrap(); + .upgraded_semi_honest( + (edge_weights, prev_neurons), + |ctx, (edge_weights, prev_neurons)| async move { + let edge_weights1 = BitDecomposed::transposed_from(&edge_weights).unwrap(); + let prev_neurons = BitDecomposed::transposed_from(&prev_neurons).unwrap(); + let edge_weights = repeat(edge_weights1).take(256).collect::>(); + let result = one_layer::<_, DefaultBitStep, 256>( + ctx.set_total_records(1), + &prev_neurons, + edge_weights.as_slice(), + ) + .await + .unwrap(); - // Vec::transposed_from(&result).unwrap() - }) + // Vec::transposed_from(&result).unwrap() + }, + ) .await; - }); } } From 2790f3e3d76811821372ab2b61c0ffc2cbf9656a Mon Sep 17 00:00:00 2001 From: Richa Jain Date: Tue, 9 Jul 2024 16:34:46 +0530 Subject: [PATCH 05/11] Test compiles --- .../protocol/ipa_prf/boolean_ops/sigmoid.rs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs index 6a2d7f004..0294d4c45 100644 --- a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs +++ b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs @@ -174,22 +174,23 @@ where // // edge_weights[0] holds all the edge weights from all the neurons from the previous layer to neuron[0] in the current layer -pub async fn one_layer( +pub async fn one_layer( ctx: C, last_layer_neurons: &BitDecomposed>, - edge_weights: &[BitDecomposed>; N], + edge_weights: I, ) -> Result>, Error> where C: Context, S: NBitStep, Boolean: FieldSimd, AdditiveShare: BooleanProtocols, + I: IntoIterator>>, { let edge_activations = ctx .parallel_join( - zip(edge_weights.iter(), repeat(last_layer_neurons)) + zip(edge_weights, repeat(last_layer_neurons)) .enumerate() - .map(|(i, (edge_weight, neuron))| { + .map(|(i, (edge_weight, last_layer_neurons))| { let ctx = ctx.narrow("activation_times_edge_weight"); async move { integer_mul::<_, S, N>( @@ -202,12 +203,12 @@ where } }), ) - .await; + .await?; // now add the last N elements in 1 BitDecomposed let aggregated_edge_weights = aggregate_values::<_, BA16, N>( ctx.narrow("aggregated_edge_weights"), - stream::iter(edge_activations).boxed(), + Box::pin(stream::iter(edge_activations.into_iter()).map(Ok)), N, ) .await?; @@ -311,19 +312,20 @@ mod test { run(|| async move { let world = TestWorld::default(); - let edge_weights = (0..256).map(|i| BA8::truncate_from(u128::try_from(i).unwrap())); + let edge_weights = (0..256) + .map(|i| BA8::truncate_from(u128::try_from(i).unwrap())); + let prev_neurons = (0..256).map(|i| BA8::truncate_from(u128::try_from(i).unwrap())); let result = world .upgraded_semi_honest( (edge_weights, prev_neurons), |ctx, (edge_weights, prev_neurons)| async move { - let edge_weights1 = BitDecomposed::transposed_from(&edge_weights).unwrap(); + let edge_weights = BitDecomposed::transposed_from(&edge_weights).unwrap(); let prev_neurons = BitDecomposed::transposed_from(&prev_neurons).unwrap(); - let edge_weights = repeat(edge_weights1).take(256).collect::>(); - let result = one_layer::<_, DefaultBitStep, 256>( + let result = one_layer::<_, DefaultBitStep, _, 256>( ctx.set_total_records(1), &prev_neurons, - edge_weights.as_slice(), + repeat(edge_weights).take(256), ) .await .unwrap(); From 3a5b75e3f384009ad92cf2010e0b4fdb098dee77 Mon Sep 17 00:00:00 2001 From: Ben Savage Date: Tue, 9 Jul 2024 19:29:58 +0800 Subject: [PATCH 06/11] It runs now --- .../protocol/ipa_prf/boolean_ops/sigmoid.rs | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs index 0294d4c45..ce7fb9952 100644 --- a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs +++ b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs @@ -1,5 +1,12 @@ -use super::{multiplication::integer_mul}; -use futures::{stream, StreamExt}; +use std::{ + iter::{repeat, zip}, + ops::Not, +}; + +use futures::{ + future::{try_join, try_join4, try_join5}, + stream, StreamExt, +}; use crate::{ error::Error, @@ -7,7 +14,7 @@ use crate::{ protocol::{ basics::mul::SecureMul, boolean::{ - step::{ThirtyTwoBitStep}, + step::{ThirtyTwoBitStep, TwoHundredFiftySixBitOpStep}, NBitStep, }, context::Context, @@ -16,11 +23,8 @@ use crate::{ }, secret_sharing::{replicated::semi_honest::AdditiveShare, BitDecomposed, FieldSimd}, }; -use futures::future::{try_join, try_join4, try_join5}; -use std::{ - iter::{repeat, zip}, - ops::Not, -}; + +use super::multiplication::integer_mul; async fn a_times_b_and_not_b( ctx: &C, @@ -186,16 +190,17 @@ where AdditiveShare: BooleanProtocols, I: IntoIterator>>, { + let c = ctx.narrow("activation_times_edge_weight"); let edge_activations = ctx .parallel_join( zip(edge_weights, repeat(last_layer_neurons)) .enumerate() .map(|(i, (edge_weight, last_layer_neurons))| { - let ctx = ctx.narrow("activation_times_edge_weight"); + let per_neuron_ctx = c.narrow(&TwoHundredFiftySixBitOpStep::from(i)); async move { integer_mul::<_, S, N>( - ctx, - RecordId::from(i), + per_neuron_ctx, + RecordId::FIRST, &edge_weight, &last_layer_neurons, ) @@ -223,8 +228,9 @@ where #[cfg(all(test, unit_test))] mod test { - use std::{num::TryFromIntError, iter::repeat}; + use std::{iter::repeat, num::TryFromIntError}; + use super::one_layer; use crate::{ ff::{boolean_array::BA8, U128Conversions}, protocol::{ @@ -236,8 +242,6 @@ mod test { test_fixture::{Reconstruct, Runner, TestWorld}, }; - use super::one_layer; - fn piecewise_linear_sigmoid_approximation(x: i128) -> Result { Ok(match x { i128::MIN..=-113 => 0, @@ -312,11 +316,10 @@ mod test { run(|| async move { let world = TestWorld::default(); - let edge_weights = (0..256) - .map(|i| BA8::truncate_from(u128::try_from(i).unwrap())); + let edge_weights = (0..256).map(|i| BA8::truncate_from(u128::try_from(i).unwrap())); let prev_neurons = (0..256).map(|i| BA8::truncate_from(u128::try_from(i).unwrap())); - let result = world + let result: Vec = world .upgraded_semi_honest( (edge_weights, prev_neurons), |ctx, (edge_weights, prev_neurons)| async move { @@ -330,10 +333,13 @@ mod test { .await .unwrap(); - // Vec::transposed_from(&result).unwrap() + Vec::transposed_from(&result).unwrap() }, ) - .await; + .await + .reconstruct(); + + println!("output of this layer: {:?}", result); }); } } From ff41a68fa014bd5e1af4f94d573bef7fbda2a2ab Mon Sep 17 00:00:00 2001 From: Ben Savage Date: Tue, 9 Jul 2024 22:48:12 +0800 Subject: [PATCH 07/11] closer to functional --- .../protocol/ipa_prf/boolean_ops/sigmoid.rs | 60 +++++++++++++++---- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs index ce7fb9952..a71bda167 100644 --- a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs +++ b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs @@ -1,4 +1,5 @@ use std::{ + convert::Infallible, iter::{repeat, zip}, ops::Not, }; @@ -8,9 +9,13 @@ use futures::{ stream, StreamExt, }; +use super::multiplication::integer_mul; use crate::{ error::Error, - ff::{boolean::Boolean, boolean_array::BA16}, + ff::{ + boolean::Boolean, + boolean_array::{BA16, BA8}, + }, protocol::{ basics::mul::SecureMul, boolean::{ @@ -21,11 +26,11 @@ use crate::{ ipa_prf::aggregation::aggregate_values, BooleanProtocols, RecordId, }, - secret_sharing::{replicated::semi_honest::AdditiveShare, BitDecomposed, FieldSimd}, + secret_sharing::{ + replicated::semi_honest::AdditiveShare, Additive, BitDecomposed, FieldSimd, TransposeFrom, + }, }; -use super::multiplication::integer_mul; - async fn a_times_b_and_not_b( ctx: &C, record_id: RecordId, @@ -189,29 +194,48 @@ where Boolean: FieldSimd, AdditiveShare: BooleanProtocols, I: IntoIterator>>, + Vec>>: for<'a> TransposeFrom< + &'a Vec>>, + Error = Infallible, + >, { let c = ctx.narrow("activation_times_edge_weight"); - let edge_activations = ctx + // Vector of N neurons in the next layer + // For each, there is a BitDecomposed of 8 bits + // Which is vectorized N across + let edge_activations: Vec>> = ctx .parallel_join( zip(edge_weights, repeat(last_layer_neurons)) .enumerate() .map(|(i, (edge_weight, last_layer_neurons))| { let per_neuron_ctx = c.narrow(&TwoHundredFiftySixBitOpStep::from(i)); async move { - integer_mul::<_, S, N>( + let lossless_result = integer_mul::<_, S, N>( per_neuron_ctx, RecordId::FIRST, &edge_weight, &last_layer_neurons, ) - .await + .await?; + // Neuron activtion is an 8-bit value meant to represent a + // fractional number in the range [0, 1) + // So after multiplying this value with the edge weight, + // we must shift 8 bits down to effectively divide by 256 + let (_, top_8_bits) = lossless_result.split_at(8); + Ok::<_, Error>(top_8_bits) } }), ) .await?; + // Vector of N incoming contributions, + // each of which is a BitDecomposed of 8 bits + // Vectorized across all N neurons in the output layer + let edge_activations: Vec>> = + Vec::transposed_from(&edge_activations).unwrap(); + // now add the last N elements in 1 BitDecomposed - let aggregated_edge_weights = aggregate_values::<_, BA16, N>( + let aggregated_edge_weights = aggregate_values::<_, BA8, N>( ctx.narrow("aggregated_edge_weights"), Box::pin(stream::iter(edge_activations.into_iter()).map(Ok)), N, @@ -228,7 +252,10 @@ where #[cfg(all(test, unit_test))] mod test { - use std::{iter::repeat, num::TryFromIntError}; + use std::{ + iter::{repeat, zip}, + num::TryFromIntError, + }; use super::one_layer; use crate::{ @@ -317,11 +344,11 @@ mod test { let world = TestWorld::default(); let edge_weights = (0..256).map(|i| BA8::truncate_from(u128::try_from(i).unwrap())); - let prev_neurons = (0..256).map(|i| BA8::truncate_from(u128::try_from(i).unwrap())); + let result: Vec = world .upgraded_semi_honest( - (edge_weights, prev_neurons), + (edge_weights.clone(), prev_neurons.clone()), |ctx, (edge_weights, prev_neurons)| async move { let edge_weights = BitDecomposed::transposed_from(&edge_weights).unwrap(); let prev_neurons = BitDecomposed::transposed_from(&prev_neurons).unwrap(); @@ -339,7 +366,16 @@ mod test { .await .reconstruct(); - println!("output of this layer: {:?}", result); + let aggregate_input = zip(edge_weights, prev_neurons).fold(0, |acc, (e, n)| { + let lossless = as_i128(e) * i128::try_from(n.as_u128()).unwrap(); + acc + (lossless >> 8) + }); + println!("aggregate input: {:?}", aggregate_input); + let expected_activation = + piecewise_linear_sigmoid_approximation(aggregate_input).unwrap(); + println!("expected activation: {:?}", expected_activation); + + assert_eq!(result.first().unwrap().as_u128(), expected_activation); }); } } From 80f3f151470ed6b4a028dc22d056b3163a63d194 Mon Sep 17 00:00:00 2001 From: Ben Savage Date: Wed, 10 Jul 2024 02:40:59 +0800 Subject: [PATCH 08/11] Very close... but tests fail --- .../protocol/ipa_prf/boolean_ops/sigmoid.rs | 173 +++++++++--------- 1 file changed, 91 insertions(+), 82 deletions(-) diff --git a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs index a71bda167..b170ed9e5 100644 --- a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs +++ b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs @@ -1,5 +1,4 @@ use std::{ - convert::Infallible, iter::{repeat, zip}, ops::Not, }; @@ -11,23 +10,18 @@ use futures::{ use super::multiplication::integer_mul; use crate::{ - error::Error, - ff::{ - boolean::Boolean, - boolean_array::{BA16, BA8}, - }, + error::{Error, LengthError}, + ff::{boolean::Boolean, boolean_array::BA8}, + helpers::{repeat_n, TotalRecords}, protocol::{ basics::mul::SecureMul, - boolean::{ - step::{ThirtyTwoBitStep, TwoHundredFiftySixBitOpStep}, - NBitStep, - }, + boolean::{step::ThirtyTwoBitStep, NBitStep}, context::Context, ipa_prf::aggregation::aggregate_values, BooleanProtocols, RecordId, }, secret_sharing::{ - replicated::semi_honest::AdditiveShare, Additive, BitDecomposed, FieldSimd, TransposeFrom, + replicated::semi_honest::AdditiveShare, BitDecomposed, FieldSimd, TransposeFrom, }, }; @@ -177,15 +171,10 @@ where ])) } -// Sigmoid( -// Sum(i = 1..N, neuron(i) in last layer activation times edge weight connecting that neuron to this) -// ) -// - -// edge_weights[0] holds all the edge weights from all the neurons from the previous layer to neuron[0] in the current layer +// edge_weights[0] holds all the edge weights coming _out_ from the first neuron in the previous layer pub async fn one_layer( ctx: C, - last_layer_neurons: &BitDecomposed>, + last_layer_neurons: Vec>, edge_weights: I, ) -> Result>, Error> where @@ -194,68 +183,59 @@ where Boolean: FieldSimd, AdditiveShare: BooleanProtocols, I: IntoIterator>>, - Vec>>: for<'a> TransposeFrom< - &'a Vec>>, - Error = Infallible, - >, + BitDecomposed>: + for<'a> TransposeFrom<&'a Vec>, Error = LengthError>, { - let c = ctx.narrow("activation_times_edge_weight"); - // Vector of N neurons in the next layer - // For each, there is a BitDecomposed of 8 bits - // Which is vectorized N across - let edge_activations: Vec>> = ctx - .parallel_join( - zip(edge_weights, repeat(last_layer_neurons)) - .enumerate() - .map(|(i, (edge_weight, last_layer_neurons))| { - let per_neuron_ctx = c.narrow(&TwoHundredFiftySixBitOpStep::from(i)); - async move { - let lossless_result = integer_mul::<_, S, N>( - per_neuron_ctx, - RecordId::FIRST, - &edge_weight, - &last_layer_neurons, - ) - .await?; - // Neuron activtion is an 8-bit value meant to represent a - // fractional number in the range [0, 1) - // So after multiplying this value with the edge weight, - // we must shift 8 bits down to effectively divide by 256 - let (_, top_8_bits) = lossless_result.split_at(8); - Ok::<_, Error>(top_8_bits) - } - }), - ) - .await?; + let multiplication_ctx = ctx.narrow("activation_times_edge_weight"); - // Vector of N incoming contributions, - // each of which is a BitDecomposed of 8 bits - // Vectorized across all N neurons in the output layer - let edge_activations: Vec>> = - Vec::transposed_from(&edge_activations).unwrap(); + let contributions_per_neuron_in_last_layer: Vec>> = ctx + .parallel_join(zip(edge_weights, last_layer_neurons).enumerate().map( + |(i, (outbound_edge_weights, last_layer_neuron))| { + let repeated_neuron_activation = BitDecomposed::transposed_from( + &repeat_n(last_layer_neuron, N).collect::>(), + ) + .unwrap(); + let c = multiplication_ctx.clone(); + async move { + let lossless_result = integer_mul::<_, S, N>( + c, + RecordId::from(i), + &repeated_neuron_activation, + &outbound_edge_weights, + ) + .await?; + // Neuron activtion is an 8-bit value meant to represent a + // fractional number in the range [0, 1) + // So after multiplying this value with the edge weight, + // we must shift 8 bits down to effectively divide by 256 + let (_, top_8_bits) = lossless_result.split_at(8); + Ok::<_, Error>(top_8_bits) + } + }, + )) + .await?; - // now add the last N elements in 1 BitDecomposed - let aggregated_edge_weights = aggregate_values::<_, BA8, N>( + let total_input = aggregate_values::<_, BA8, N>( ctx.narrow("aggregated_edge_weights"), - Box::pin(stream::iter(edge_activations.into_iter()).map(Ok)), + Box::pin(stream::iter(contributions_per_neuron_in_last_layer.into_iter()).map(Ok)), N, ) .await?; sigmoid::<_, N>( - ctx.narrow("sigmoid"), + ctx.narrow("sigmoid") + .set_total_records(TotalRecords::Indeterminate), RecordId::FIRST, - &aggregated_edge_weights, + &total_input, ) .await } #[cfg(all(test, unit_test))] mod test { - use std::{ - iter::{repeat, zip}, - num::TryFromIntError, - }; + use std::{iter::zip, num::TryFromIntError}; + + use rand::{thread_rng, Rng}; use super::one_layer; use crate::{ @@ -343,19 +323,30 @@ mod test { run(|| async move { let world = TestWorld::default(); - let edge_weights = (0..256).map(|i| BA8::truncate_from(u128::try_from(i).unwrap())); - let prev_neurons = (0..256).map(|i| BA8::truncate_from(u128::try_from(i).unwrap())); + let mut rng = thread_rng(); + + let edge_weights_matrix = (0..256) + .map(|_| (0..256).map(|_| rng.gen::()).collect::>()) + .collect::>(); + let prev_neurons = (0..256).map(|_| rng.gen::()).collect::>(); let result: Vec = world .upgraded_semi_honest( - (edge_weights.clone(), prev_neurons.clone()), + ( + edge_weights_matrix + .clone() + .into_iter() + .map(|x| x.into_iter()), + prev_neurons.clone().into_iter(), + ), |ctx, (edge_weights, prev_neurons)| async move { - let edge_weights = BitDecomposed::transposed_from(&edge_weights).unwrap(); - let prev_neurons = BitDecomposed::transposed_from(&prev_neurons).unwrap(); + let matrix_of_edge_weights = edge_weights + .iter() + .map(|chunk| BitDecomposed::transposed_from(chunk).unwrap()); let result = one_layer::<_, DefaultBitStep, _, 256>( - ctx.set_total_records(1), - &prev_neurons, - repeat(edge_weights).take(256), + ctx.set_total_records(256), + prev_neurons, + matrix_of_edge_weights, ) .await .unwrap(); @@ -366,16 +357,34 @@ mod test { .await .reconstruct(); - let aggregate_input = zip(edge_weights, prev_neurons).fold(0, |acc, (e, n)| { - let lossless = as_i128(e) * i128::try_from(n.as_u128()).unwrap(); - acc + (lossless >> 8) - }); - println!("aggregate input: {:?}", aggregate_input); - let expected_activation = - piecewise_linear_sigmoid_approximation(aggregate_input).unwrap(); - println!("expected activation: {:?}", expected_activation); - - assert_eq!(result.first().unwrap().as_u128(), expected_activation); + let expected_activations = zip(edge_weights_matrix, prev_neurons) + .fold([0; 256], |mut acc, (edge_weights, n)| { + let contributions_from_neuron = edge_weights.into_iter().map(|e| { + let lossless = as_i128(e) * i128::try_from(n.as_u128()).unwrap(); + lossless >> 8 + }); + + acc.iter_mut() + .zip(contributions_from_neuron) + .for_each(|(a, c)| *a += c); + acc + }) + .map(|total_input| { + ( + total_input, + piecewise_linear_sigmoid_approximation(total_input).unwrap(), + ) + }); + + for ((total_input, expected_activation), actual_result) in + expected_activations.iter().zip(result) + { + println!( + "total_input: {:?}, expected_activation: {:?}, actual_result: {:?}", + total_input, expected_activation, actual_result + ); + assert_eq!(actual_result.as_u128(), *expected_activation); + } }); } } From 2bfa6702d368bd6667d17cd393dd8534fd6448fb Mon Sep 17 00:00:00 2001 From: Ben Savage Date: Wed, 10 Jul 2024 03:14:15 +0800 Subject: [PATCH 09/11] Test passes only if every edge weight is the same --- .../protocol/ipa_prf/boolean_ops/sigmoid.rs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs index b170ed9e5..5f5f6613b 100644 --- a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs +++ b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs @@ -11,7 +11,7 @@ use futures::{ use super::multiplication::integer_mul; use crate::{ error::{Error, LengthError}, - ff::{boolean::Boolean, boolean_array::BA8}, + ff::{boolean::Boolean, boolean_array::{BA16, BA8}}, helpers::{repeat_n, TotalRecords}, protocol::{ basics::mul::SecureMul, @@ -215,18 +215,20 @@ where )) .await?; - let total_input = aggregate_values::<_, BA8, N>( + let total_input = aggregate_values::<_, BA16, N>( ctx.narrow("aggregated_edge_weights"), Box::pin(stream::iter(contributions_per_neuron_in_last_layer.into_iter()).map(Ok)), N, ) .await?; + let (lower_8_bits, _) = total_input.split_at(8); + sigmoid::<_, N>( ctx.narrow("sigmoid") .set_total_records(TotalRecords::Indeterminate), RecordId::FIRST, - &total_input, + &lower_8_bits, ) .await } @@ -325,10 +327,10 @@ mod test { let mut rng = thread_rng(); - let edge_weights_matrix = (0..256) - .map(|_| (0..256).map(|_| rng.gen::()).collect::>()) + let edge_weights_matrix = (0..32) + .map(|_| (0..32).map(|_| BA8::truncate_from(139_u128)).collect::>()) .collect::>(); - let prev_neurons = (0..256).map(|_| rng.gen::()).collect::>(); + let prev_neurons = (0..32).map(|_| rng.gen::()).collect::>(); let result: Vec = world .upgraded_semi_honest( @@ -343,8 +345,8 @@ mod test { let matrix_of_edge_weights = edge_weights .iter() .map(|chunk| BitDecomposed::transposed_from(chunk).unwrap()); - let result = one_layer::<_, DefaultBitStep, _, 256>( - ctx.set_total_records(256), + let result = one_layer::<_, DefaultBitStep, _, 32>( + ctx.set_total_records(32), prev_neurons, matrix_of_edge_weights, ) @@ -358,7 +360,7 @@ mod test { .reconstruct(); let expected_activations = zip(edge_weights_matrix, prev_neurons) - .fold([0; 256], |mut acc, (edge_weights, n)| { + .fold([0; 32], |mut acc, (edge_weights, n)| { let contributions_from_neuron = edge_weights.into_iter().map(|e| { let lossless = as_i128(e) * i128::try_from(n.as_u128()).unwrap(); lossless >> 8 From 85f1e447dae41c8302b01ebbeda82d42990628aa Mon Sep 17 00:00:00 2001 From: Ben Savage Date: Wed, 10 Jul 2024 14:00:09 +0800 Subject: [PATCH 10/11] test passes --- .../src/protocol/ipa_prf/boolean_ops/sigmoid.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs index 5f5f6613b..f8d18efa2 100644 --- a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs +++ b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs @@ -11,7 +11,10 @@ use futures::{ use super::multiplication::integer_mul; use crate::{ error::{Error, LengthError}, - ff::{boolean::Boolean, boolean_array::{BA16, BA8}}, + ff::{ + boolean::Boolean, + boolean_array::{BA16, BA8}, + }, helpers::{repeat_n, TotalRecords}, protocol::{ basics::mul::SecureMul, @@ -328,7 +331,14 @@ mod test { let mut rng = thread_rng(); let edge_weights_matrix = (0..32) - .map(|_| (0..32).map(|_| BA8::truncate_from(139_u128)).collect::>()) + .map(|i| { + (0..32).map(|j| { + // offset is in the range [-32, 32) + let offset = (3 * i + 5 * j) % 64 - 32; + let modulo = (256 + offset) % 256; + BA8::truncate_from(modulo as u128) + }).collect::>() + }) .collect::>(); let prev_neurons = (0..32).map(|_| rng.gen::()).collect::>(); From f55a99a150d8a63a0dba49386485c351279e5220 Mon Sep 17 00:00:00 2001 From: Ben Savage Date: Wed, 10 Jul 2024 17:00:32 +0800 Subject: [PATCH 11/11] only problem is from overflow --- ipa-core/src/protocol/dp/mod.rs | 2 +- .../src/protocol/ipa_prf/aggregation/mod.rs | 72 ++++++++++++++++--- .../protocol/ipa_prf/boolean_ops/sigmoid.rs | 26 ++++--- .../prf_sharding/feature_label_dot_product.rs | 2 +- 4 files changed, 76 insertions(+), 26 deletions(-) diff --git a/ipa-core/src/protocol/dp/mod.rs b/ipa-core/src/protocol/dp/mod.rs index cde10f1b7..3510495b8 100644 --- a/ipa-core/src/protocol/dp/mod.rs +++ b/ipa-core/src/protocol/dp/mod.rs @@ -58,7 +58,7 @@ where let aggregation_input = Box::pin(stream::iter(vector_input_to_agg.into_iter()).map(Ok)); // Step 3: Call `aggregate_values` to sum up Bernoulli noise. let noise_vector: Result>, Error> = - aggregate_values::<_, OV, B>(ctx, aggregation_input, num_bernoulli as usize).await; + aggregate_values::<_, OV, B>(ctx, aggregation_input, num_bernoulli as usize, false).await; noise_vector } /// `apply_dp_noise` takes the noise distribution parameters (`num_bernoulli` and in the future `quantization_scale`) diff --git a/ipa-core/src/protocol/ipa_prf/aggregation/mod.rs b/ipa-core/src/protocol/ipa_prf/aggregation/mod.rs index 14e722fd9..9d76b5b8a 100644 --- a/ipa-core/src/protocol/ipa_prf/aggregation/mod.rs +++ b/ipa-core/src/protocol/ipa_prf/aggregation/mod.rs @@ -188,7 +188,8 @@ where .try_flatten_iters(), ); let aggregated_result = - aggregate_values::<_, HV, B>(ctx, aggregation_input, contributions_stream_len).await?; + aggregate_values::<_, HV, B>(ctx, aggregation_input, contributions_stream_len, false) + .await?; Ok(Vec::transposed_from(&aggregated_result)?) } @@ -218,6 +219,7 @@ pub async fn aggregate_values<'ctx, 'fut, C, OV, const B: usize>( ctx: C, mut aggregated_stream: Pin> + Send + 'fut>>, mut num_rows: usize, + truncate: bool, ) -> Result>, Error> where 'ctx: 'fut, @@ -271,6 +273,15 @@ where .await?; sum.push(carry); Ok(sum) + } else if truncate { + let (sum, _) = integer_add::<_, SixteenBitStep, B>( + ctx.narrow(&AggregateValuesStep::Add), + record_id, + &a, + &b, + ) + .await?; + Ok(sum) } else { assert!( OV::BITS <= SixteenBitStep::BITS, @@ -361,7 +372,12 @@ pub mod tests { let result: BitDecomposed = TestWorld::default() .upgraded_semi_honest(inputs.into_iter(), |ctx, inputs| { let num_rows = inputs.len(); - aggregate_values::<_, BA8, 8>(ctx, stream::iter(inputs).boxed(), num_rows) + aggregate_values::<_, BA8, 8>( + ctx, + stream::iter(inputs).boxed(), + num_rows, + false, + ) }) .await .map(Result::unwrap) @@ -384,7 +400,12 @@ pub mod tests { let result = TestWorld::default() .upgraded_semi_honest(inputs.into_iter(), |ctx, inputs| { let num_rows = inputs.len(); - aggregate_values::<_, BA8, 8>(ctx, stream::iter(inputs).boxed(), num_rows) + aggregate_values::<_, BA8, 8>( + ctx, + stream::iter(inputs).boxed(), + num_rows, + false, + ) }) .await .map(Result::unwrap) @@ -410,7 +431,12 @@ pub mod tests { let result = TestWorld::default() .upgraded_semi_honest(inputs.into_iter(), |ctx, inputs| { let num_rows = inputs.len(); - aggregate_values::<_, BA8, 8>(ctx, stream::iter(inputs).boxed(), num_rows) + aggregate_values::<_, BA8, 8>( + ctx, + stream::iter(inputs).boxed(), + num_rows, + false, + ) }) .await .map(Result::unwrap) @@ -438,7 +464,12 @@ pub mod tests { let result = TestWorld::default() .upgraded_semi_honest(inputs.into_iter(), |ctx, inputs| { let num_rows = inputs.len(); - aggregate_values::<_, BA8, 8>(ctx, stream::iter(inputs).boxed(), num_rows) + aggregate_values::<_, BA8, 8>( + ctx, + stream::iter(inputs).boxed(), + num_rows, + false, + ) }) .await .map(Result::unwrap) @@ -465,7 +496,12 @@ pub mod tests { let result = TestWorld::default() .upgraded_semi_honest(inputs.into_iter(), |ctx, inputs| { let num_rows = inputs.len(); - aggregate_values::<_, BA8, 8>(ctx, stream::iter(inputs).boxed(), num_rows) + aggregate_values::<_, BA8, 8>( + ctx, + stream::iter(inputs).boxed(), + num_rows, + false, + ) }) .await .map(Result::unwrap) @@ -484,7 +520,7 @@ pub mod tests { run(|| async move { let result = TestWorld::default() .upgraded_semi_honest((), |ctx, ()| { - aggregate_values::<_, BA8, 8>(ctx, stream::empty().boxed(), 0) + aggregate_values::<_, BA8, 8>(ctx, stream::empty().boxed(), 0, false) }) .await .map(Result::unwrap) @@ -505,7 +541,12 @@ pub mod tests { let result = TestWorld::default() .upgraded_semi_honest(inputs.into_iter(), |ctx, inputs| { let num_rows = inputs.len(); - aggregate_values::<_, BA8, 8>(ctx, stream::iter(inputs).boxed(), num_rows) + aggregate_values::<_, BA8, 8>( + ctx, + stream::iter(inputs).boxed(), + num_rows, + false, + ) }) .await; @@ -525,7 +566,12 @@ pub mod tests { let _ = TestWorld::default() .upgraded_semi_honest(inputs.into_iter(), |ctx, inputs| { let num_rows = inputs.len() + 1; - aggregate_values::<_, BA8, 8>(ctx, stream::iter(inputs).boxed(), num_rows) + aggregate_values::<_, BA8, 8>( + ctx, + stream::iter(inputs).boxed(), + num_rows, + false, + ) }) .await .map(Result::unwrap) @@ -547,7 +593,12 @@ pub mod tests { let _ = TestWorld::default() .upgraded_semi_honest(inputs.into_iter(), |ctx, inputs| { let num_rows = inputs.len() - 1; - aggregate_values::<_, BA8, 8>(ctx, stream::iter(inputs).boxed(), num_rows) + aggregate_values::<_, BA8, 8>( + ctx, + stream::iter(inputs).boxed(), + num_rows, + false, + ) }) .await .map(Result::unwrap) @@ -633,6 +684,7 @@ pub mod tests { ctx, stream::iter(inputs).boxed(), num_rows, + false, ) }) .await diff --git a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs index f8d18efa2..9fdebad6a 100644 --- a/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs +++ b/ipa-core/src/protocol/ipa_prf/boolean_ops/sigmoid.rs @@ -11,10 +11,7 @@ use futures::{ use super::multiplication::integer_mul; use crate::{ error::{Error, LengthError}, - ff::{ - boolean::Boolean, - boolean_array::{BA16, BA8}, - }, + ff::{boolean::Boolean, boolean_array::BA8}, helpers::{repeat_n, TotalRecords}, protocol::{ basics::mul::SecureMul, @@ -218,20 +215,19 @@ where )) .await?; - let total_input = aggregate_values::<_, BA16, N>( + let total_input = aggregate_values::<_, BA8, N>( ctx.narrow("aggregated_edge_weights"), Box::pin(stream::iter(contributions_per_neuron_in_last_layer.into_iter()).map(Ok)), N, + true, ) .await?; - let (lower_8_bits, _) = total_input.split_at(8); - sigmoid::<_, N>( ctx.narrow("sigmoid") .set_total_records(TotalRecords::Indeterminate), RecordId::FIRST, - &lower_8_bits, + &total_input, ) .await } @@ -332,12 +328,14 @@ mod test { let edge_weights_matrix = (0..32) .map(|i| { - (0..32).map(|j| { - // offset is in the range [-32, 32) - let offset = (3 * i + 5 * j) % 64 - 32; - let modulo = (256 + offset) % 256; - BA8::truncate_from(modulo as u128) - }).collect::>() + (0..32) + .map(|j| { + // offset is in the range [-16, 16) + let offset = (3 * i + 5 * j) % 32 - 16; + let modulo = (256 + offset) % 256; + BA8::truncate_from(modulo as u128) + }) + .collect::>() }) .collect::>(); let prev_neurons = (0..32).map(|_| rng.gen::()).collect::>(); diff --git a/ipa-core/src/protocol/ipa_prf/prf_sharding/feature_label_dot_product.rs b/ipa-core/src/protocol/ipa_prf/prf_sharding/feature_label_dot_product.rs index 024042f41..f1eeb3d06 100644 --- a/ipa-core/src/protocol/ipa_prf/prf_sharding/feature_label_dot_product.rs +++ b/ipa-core/src/protocol/ipa_prf/prf_sharding/feature_label_dot_product.rs @@ -264,7 +264,7 @@ where seq_join(sh_ctx.active_work(), stream::iter(chunked_user_results)).try_flatten_iters(), ); let aggregated_result: BitDecomposed> = - aggregate_values::<_, HV, B>(binary_m_ctx, flattened_stream, num_outputs).await?; + aggregate_values::<_, HV, B>(binary_m_ctx, flattened_stream, num_outputs, false).await?; let transposed_aggregated_result: Vec> = Vec::transposed_from(&aggregated_result)?;