Skip to content
This repository has been archived by the owner on Feb 29, 2024. It is now read-only.

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
ImmanuelSegol committed Jan 20, 2024
1 parent 743af92 commit d7fdb87
Showing 1 changed file with 85 additions and 1 deletion.
86 changes: 85 additions & 1 deletion docs/icicle/rust-bindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,92 @@ icicle-bn254 = { git = "https://github.com/ingonyama-zk/icicle.git", tag = "v1.0
`icicle-bn254` being the curve you wish to use and `icicle-core` and `icicle-cuda-runtime` contain ICICLE utilities and CUDA wrappers.

If you wish to point to a specific ICICLE branch add `branch = "<name_of_branch>"` or `tag = "<name_of_tag>"` to the ICICLE dependency. For a specific commit add `rev = "<commit_id>"`.

Lets write a quick example to demonstrate how you would use the Rust ICICLE library. Create a file named `main.rs` in your project, with the following:

When you build your project ICICLE will be built as part of the build command.
```rust
use icicle_bn254::curve::{
ScalarCfg,
ScalarField,
};

use icicle_bls12_377::curve::{
ScalarCfg as BLS12377ScalarCfg,
ScalarField as BLS12377ScalarField
};

use icicle_cuda_runtime::{
stream::CudaStream,
memory::DeviceSlice,
device_context::get_default_device_context
};

use icicle_core::{
ntt::{self, NTT},
traits::{GenerateRandom, FieldImpl}
};

use icicle_core::traits::ArkConvertible;

use ark_bn254::Fr as Bn254Fr;
use ark_bls12_377::Fr as Bls12377Fr;
use ark_ff::FftField;
use ark_poly::{EvaluationDomain, Radix2EvaluationDomain};
use ark_std::cmp::{Ord, Ordering};
use std::convert::TryInto;

#[cfg(feature = "profile")]
use std::time::Instant;

use clap::Parser;

fn main() {
let size = 1 << 23;
let scalars = ScalarCfg::generate_random(size);
let mut ntt_results: DeviceSlice<'_, ScalarField> = DeviceSlice::cuda_malloc(size).unwrap();

let icicle_omega = <Bn254Fr as FftField>::get_root_of_unity(size.try_into().unwrap()).unwrap();
let ctx = get_default_device_context();
ScalarCfg::initialize_domain(ScalarField::from_ark(icicle_omega), &ctx).unwrap();

let stream = CudaStream::create().unwrap();
let mut cfg = ntt::get_default_ntt_config::<ScalarField>();
cfg.ctx.stream = &stream;
cfg.is_async = true;
cfg.are_outputs_on_device = true;

ntt::ntt(scalars.as_slice(), ntt::NTTDir::kForward, &cfg, ntt_results.as_slice()).unwrap();

stream.synchronize().unwrap();
let mut host_bn254_results = vec![ScalarField::zero(); size];
ntt_results.copy_to_host(&mut host_bn254_results[..]).unwrap();

stream.destroy().unwrap();
}
```

Using `ScalarCfg::generate_random` we generate random inputs for our `NTT` method, we also initialize the domain `initialize_domain` this generates twiddle factors which will be used to compute NTTs.

We also create a CUDA stream by calling `CudaStream::create().unwrap()`, this stream will be used to send the data over to the GPU and return the result.


By calling `ntt::ntt(scalars.as_slice(), ntt::NTTDir::kForward, &cfg, ntt_results.as_slice()).unwrap();` we send the configurations, scalars and result pointer over to the GPU to preform the NTT computation.

By default CUDA kernels run in parallel to the CPU, so after triggering your `ntt` method if you need to wait for the result you must synchronize your stream. This can be done easily with `stream.synchronize().unwrap();` your CPU thread will now wait for all CUDA kernels to resolve.

To retrieve the result you must copy the result from device to host.

```
...
ntt_results.copy_to_host(&mut host_bn254_results[..]).unwrap();
...
```

Finally to clean up just destroy the stream you have been using.

This example covers the basic usage of the ICICLE Rust bindings.

For more examples refer [here](https://github.com/ingonyama-zk/icicle/tree/main/examples).


# How do the rust bindings work?
Expand Down

0 comments on commit d7fdb87

Please sign in to comment.