ICICLE Golang Usage Guide
Overview​
This guide covers the usage of ICICLE's Golang API, including device management, memory operations, data transfer, synchronization, and compute APIs.
Device Management​
See all ICICLE runtime APIs in runtime.go
Loading a Backend​
The backend can be loaded from a specific path or from an environment variable. This is essential for setting up the computing environment.
import "github.com/ingonyama-zk/icicle/v3/wrappers/golang/runtime"
result := runtime.LoadBackendFromEnvOrDefault()
// or load from custom install dir
result := runtime.LoadBackend("/path/to/backend/installdir", true)
Setting and Getting Active Device​
You can set the active device for the current thread and retrieve it when needed:
device = runtime.CreateDevice("CUDA", 0) // or other
result := runtime.SetDevice(device)
// or query current (thread) device
activeDevice := runtime.GetActiveDevice()
Querying Device Information​
Retrieve the number of available devices and check if a pointer is allocated on the host or on the active device:
numDevices := runtime.GetDeviceCount()
var ptr unsafe.Pointer
isHostMemory = runtime.IsHostMemory(ptr)
isDeviceMemory = runtime.IsActiveDeviceMemory(ptr)
Memory Management​
Allocating and Freeing Memory​
Memory can be allocated and freed on the active device:
ptr, err := runtime.Malloc(1024) // Allocate 1024 bytes
err := runtime.Free(ptr) // Free the allocated memory
Asynchronous Memory Operations​
You can perform memory allocation and deallocation asynchronously using streams:
stream, err := runtime.CreateStream()
ptr, err := runtime.MallocAsync(1024, stream)
err = runtime.FreeAsync(ptr, stream)
Querying Available Memory​
Retrieve the total and available memory on the active device:
size_t total_memory, available_memory;
availableMemory, err := runtime.GetAvailableMemory()
freeMemory := availableMemory.Free
totalMemory := availableMemory.Total
Setting Memory Values​
Set memory to a specific value on the active device, synchronously or asynchronously:
err := runtime.Memset(ptr, 0, 1024) // Set 1024 bytes to 0
err := runtime.MemsetAsync(ptr, 0, 1024, stream)
Data Transfer​
Explicit Data Transfers​
To avoid device-inference overhead, use explicit copy functions:
result := runtime.CopyToHost(host_dst, device_src, size)
result := runtime.CopyToHostAsync(host_dst, device_src, size, stream)
result := runtime.CopyToDevice(device_dst, host_src, size)
result := runtime.CopyToDeviceAsync(device_dst, host_src, size, stream)
Stream Management​
Creating and Destroying Streams​
Streams are used to manage asynchronous operations:
stream, err := runtime.CreateStream()
err = runtime.DestroyStream(stream)
Synchronization​
Synchronizing Streams and Devices​
Ensure all previous operations on a stream or device are completed before proceeding:
err := runtime.StreamSynchronize(stream)
err := runtime.DeviceSynchronize()
Device Properties​
Checking Device Availability​
Check if a device is available and retrieve a list of registered devices:
dev := runtime.CreateDevice("CPU", 0)
isCPUAvail := runtime.IsDeviceAvailable(dev)
Querying Device Properties​
Retrieve properties of the active device:
properties, err := runtime.GetDeviceProperties(properties);
/******************/
// where DeviceProperties is
type DeviceProperties struct {
UsingHostMemory bool // Indicates if the device uses host memory
NumMemoryRegions int32 // Number of memory regions available on the device
SupportsPinnedMemory bool // Indicates if the device supports pinned memory
}
Compute APIs​
Multi-Scalar Multiplication (MSM) Example​
Icicle provides high-performance compute APIs such as the Multi-Scalar Multiplication (MSM) for cryptographic operations. Here's a simple example of how to use the MSM API.
package main
import (
"fmt"
"github.com/ingonyama-zk/icicle/v3/wrappers/golang/core"
"github.com/ingonyama-zk/icicle/v3/wrappers/golang/runtime"
"github.com/ingonyama-zk/icicle/v3/wrappers/golang/curves/bn254"
bn254Msm "github.com/ingonyama-zk/icicle/v3/wrappers/golang/curves/bn254/msm"
)
func main() {
// Load installed backends
runtime.LoadBackendFromEnvOrDefault()
// trying to choose CUDA if available, or fallback to CPU otherwise (default device)
deviceCuda := runtime.CreateDevice("CUDA", 0) // GPU-0
if runtime.IsDeviceAvailable(&deviceCuda) {
runtime.SetDevice(&deviceCuda)
} // else we stay on CPU backend
// Setup inputs
const size = 1 << 18
// Generate random inputs
scalars := bn254.GenerateScalars(size)
points := bn254.GenerateAffinePoints(size)
// (optional) copy scalars to device memory explicitly
var scalarsDevice core.DeviceSlice
scalars.CopyToDevice(&scalarsDevice, true)
// MSM configuration
cfgBn254 := core.GetDefaultMSMConfig()
// allocate memory for the result
result := make(core.HostSlice[bn254.Projective], 1)
// execute bn254 MSM on device
err := bn254Msm.Msm(scalarsDevice, points, &cfgBn254, result)
// Check for errors
if err != runtime.Success {
errorString := fmt.Sprint(
"bn254 Msm failed: ", err)
panic(errorString)
}
// free explicitly allocated device memory
scalarsDevice.Free()
}
Polynomial Operations Example​
Here's another example demonstrating polynomial operations using Icicle:
package main
import (
"fmt"
"github.com/ingonyama-zk/icicle/v3/wrappers/golang/core"
"github.com/ingonyama-zk/icicle/v3/wrappers/golang/runtime"
"github.com/ingonyama-zk/icicle/v3/wrappers/golang/fields/babybear"
"github.com/ingonyama-zk/icicle/v3/wrappers/golang/fields/babybear/ntt"
"github.com/ingonyama-zk/icicle/v3/wrappers/golang/fields/babybear/polynomial"
)
func initBabybearDomain() runtime.EIcicleError {
cfgInitDomain := core.GetDefaultNTTInitDomainConfig()
rouIcicle := babybear.ScalarField{}
rouIcicle.FromUint32(1461624142)
return ntt.InitDomain(rouIcicle, cfgInitDomain)
}
func init() {
// Load installed backends
runtime.LoadBackendFromEnvOrDefault()
// trying to choose CUDA if available, or fallback to CPU otherwise (default device)
deviceCuda := runtime.CreateDevice("CUDA", 0) // GPU-0
if runtime.IsDeviceAvailable(&deviceCuda) {
runtime.SetDevice(&deviceCuda)
} // else we stay on CPU backend
// build domain for ntt is required for some polynomial ops that rely on ntt
err := initBabybearDomain()
if err != runtime.Success {
errorString := fmt.Sprint(
"Babybear Domain initialization failed: ", err)
panic(errorString)
}
}
func main() {
// Setup inputs
const polySize = 1 << 10
// randomize two polynomials over babybear field
var fBabybear polynomial.DensePolynomial
defer fBabybear.Delete()
var gBabybear polynomial.DensePolynomial
defer gBabybear.Delete()
fBabybear.CreateFromCoeffecitients(babybear.GenerateScalars(polySize))
gBabybear.CreateFromCoeffecitients(babybear.GenerateScalars(polySize / 2))
// Perform polynomial multiplication
rBabybear := fBabybear.Multiply(&gBabybear) // Executes on the current device
defer rBabybear.Delete()
rDegree := rBabybear.Degree()
fmt.Println("f Degree: ", fBabybear.Degree())
fmt.Println("g Degree: ", gBabybear.Degree())
fmt.Println("r Degree: ", rDegree)
}
In this example, the polynomial multiplication is used to perform polynomial multiplication on CUDA or CPU, showcasing the flexibility and power of Icicle's compute APIs.
Error Handling​
Checking for Errors​
Icicle APIs return an EIcicleError
enumeration value. Always check the returned value to ensure that operations were successful.
if result != runtime.SUCCESS {
// Handle error
}