Pr add prover benchmark tool (#790)
* initial setup * reorg * figuring out basic shell commands * benchmarks * benchmarks * Sets up environment for running benchmarks * updates * integrate setup and proving * updates * adding outputs * cleanup * check failure * benchmarks * benchmarks * benchmarks * benchmarks * benchmarks * benchmarks * formatting * fix running larger sizes * use larger ceremony file size * use larger ceremony file size * use larger ceremony file size * restore benchmarks * cleanup * cleanup * cleanup * cleanup * cleanup * cleanup * cleanup * cleanup * cleanup * cleanup * refactor env * refactor env * refactor env * refactor env * refactor env * rename * cleanup * cleanup * cleanup * cleanup * cleanup * cleanup * readme * readme * merge * initial splitout of codex ark prover cli * opts * copying nimcli opts * copying nimcli opts * copying nimcli opts * updating ark cli * updating ark cli * updating ark cli * updating ark cli * updating ark cli * updating ark cli * updating ark cli * updating ark cli * docs * remove file * add param * add benchmarkLoops param * update benchmark formatting * update benchmark formatting * update benchmark formatting * update benchmark formatting * fix naming * fix serde version * Apply suggestions from code review cleanup wording Signed-off-by: Dmitriy Ryajov <dryajov@gmail.com> --------- Signed-off-by: Dmitriy Ryajov <dryajov@gmail.com> Co-authored-by: Dmitriy Ryajov <dryajov@gmail.com>
This commit is contained in:
parent
3046b7636c
commit
efd46148b0
|
@ -0,0 +1,2 @@
|
|||
ceremony
|
||||
circuit_bench_*
|
|
@ -0,0 +1,33 @@
|
|||
|
||||
## Benchmark Runner
|
||||
|
||||
Modify `runAllBenchmarks` proc in `run_benchmarks.nim` to the desired parameters and variations.
|
||||
|
||||
Then run it:
|
||||
|
||||
```sh
|
||||
nim c -r run_benchmarks
|
||||
```
|
||||
|
||||
By default all circuit files for each combinations of circuit args will be generated in a unique folder named like:
|
||||
nim-codex/benchmarks/circuit_bench_depth32_maxslots256_cellsize2048_blocksize65536_nsamples9_entropy1234567_seed12345_nslots11_ncells512_index3
|
||||
|
||||
Generating the circuit files often takes longer than running benchmarks, so caching the results allows re-running the benchmark as needed.
|
||||
|
||||
You can modify the `CircuitArgs` and `CircuitEnv` objects in `runAllBenchMarks` to suite your needs. See `create_circuits.nim` for their definition.
|
||||
|
||||
The runner executes all commands relative to the `nim-codex` repo. This simplifies finding the correct circuit includes paths, etc. `CircuitEnv` sets all of this.
|
||||
|
||||
## Codex Ark Circom CLI
|
||||
|
||||
Runs Codex's prover setup with Ark / Circom.
|
||||
|
||||
Compile:
|
||||
```sh
|
||||
nim c codex_ark_prover_cli.nim
|
||||
```
|
||||
|
||||
Run to see usage:
|
||||
```sh
|
||||
./codex_ark_prover_cli.nim -h
|
||||
```
|
|
@ -0,0 +1,15 @@
|
|||
--path:
|
||||
".."
|
||||
--path:
|
||||
"../tests"
|
||||
--threads:
|
||||
on
|
||||
--tlsEmulation:
|
||||
off
|
||||
--d:
|
||||
release
|
||||
|
||||
# when not defined(chronicles_log_level):
|
||||
# --define:"chronicles_log_level:NONE" # compile all log statements
|
||||
# --define:"chronicles_sinks:textlines[dynamic]" # allow logs to be filtered at runtime
|
||||
# --"import":"logging" # ensure that logging is ignored at runtime
|
|
@ -0,0 +1,187 @@
|
|||
import std/[hashes, json, strutils, strformat, os, osproc, uri]
|
||||
|
||||
import ./utils
|
||||
|
||||
type
|
||||
CircuitEnv* = object
|
||||
nimCircuitCli*: string
|
||||
circuitDirIncludes*: string
|
||||
ptauPath*: string
|
||||
ptauUrl*: Uri
|
||||
codexProjDir*: string
|
||||
|
||||
CircuitArgs* = object
|
||||
depth*: int
|
||||
maxslots*: int
|
||||
cellsize*: int
|
||||
blocksize*: int
|
||||
nsamples*: int
|
||||
entropy*: int
|
||||
seed*: int
|
||||
nslots*: int
|
||||
ncells*: int
|
||||
index*: int
|
||||
|
||||
proc findCodexProjectDir(): string =
|
||||
## find codex proj dir -- assumes this script is in codex/benchmarks
|
||||
result = currentSourcePath().parentDir.parentDir
|
||||
|
||||
func default*(tp: typedesc[CircuitEnv]): CircuitEnv =
|
||||
let codexDir = findCodexProjectDir()
|
||||
result.nimCircuitCli =
|
||||
codexDir / "vendor" / "codex-storage-proofs-circuits" / "reference" / "nim" /
|
||||
"proof_input" / "cli"
|
||||
result.circuitDirIncludes =
|
||||
codexDir / "vendor" / "codex-storage-proofs-circuits" / "circuit"
|
||||
result.ptauPath =
|
||||
codexDir / "benchmarks" / "ceremony" / "powersOfTau28_hez_final_23.ptau"
|
||||
result.ptauUrl = "https://storage.googleapis.com/zkevm/ptau".parseUri
|
||||
result.codexProjDir = codexDir
|
||||
|
||||
proc check*(env: var CircuitEnv) =
|
||||
## check that the CWD of script is in the codex parent
|
||||
let codexProjDir = findCodexProjectDir()
|
||||
echo "\n\nFound project dir: ", codexProjDir
|
||||
|
||||
let snarkjs = findExe("snarkjs")
|
||||
if snarkjs == "":
|
||||
echo dedent"""
|
||||
ERROR: must install snarkjs first
|
||||
|
||||
npm install -g snarkjs@latest
|
||||
"""
|
||||
|
||||
let circom = findExe("circom")
|
||||
if circom == "":
|
||||
echo dedent"""
|
||||
ERROR: must install circom first
|
||||
|
||||
git clone https://github.com/iden3/circom.git
|
||||
cargo install --path circom
|
||||
"""
|
||||
|
||||
if snarkjs == "" or circom == "":
|
||||
quit 2
|
||||
|
||||
echo "Found SnarkJS: ", snarkjs
|
||||
echo "Found Circom: ", circom
|
||||
|
||||
if not env.nimCircuitCli.fileExists:
|
||||
echo "Nim Circuit reference cli not found: ", env.nimCircuitCli
|
||||
echo "Building Circuit reference cli...\n"
|
||||
withDir env.nimCircuitCli.parentDir:
|
||||
runit "nimble build -d:release --styleCheck:off cli"
|
||||
echo "CWD: ", getCurrentDir()
|
||||
assert env.nimCircuitCli.fileExists()
|
||||
|
||||
echo "Found NimCircuitCli: ", env.nimCircuitCli
|
||||
echo "Found Circuit Path: ", env.circuitDirIncludes
|
||||
echo "Found PTAU file: ", env.ptauPath
|
||||
|
||||
proc downloadPtau*(ptauPath: string, ptauUrl: Uri) =
|
||||
## download ptau file using curl if needed
|
||||
if not ptauPath.fileExists:
|
||||
echo "Ceremony file not found, downloading..."
|
||||
createDir ptauPath.parentDir
|
||||
withDir ptauPath.parentDir:
|
||||
runit fmt"curl --output '{ptauPath}' '{$ptauUrl}/{ptauPath.splitPath().tail}'"
|
||||
else:
|
||||
echo "Found PTAU file at: ", ptauPath
|
||||
|
||||
proc getCircuitBenchStr*(args: CircuitArgs): string =
|
||||
for f, v in fieldPairs(args):
|
||||
result &= "_" & f & $v
|
||||
|
||||
proc getCircuitBenchPath*(args: CircuitArgs, env: CircuitEnv): string =
|
||||
## generate folder name for unique circuit args
|
||||
result = env.codexProjDir / "benchmarks/circuit_bench" & getCircuitBenchStr(args)
|
||||
|
||||
proc generateCircomAndSamples*(args: CircuitArgs, env: CircuitEnv, name: string) =
|
||||
## run nim circuit and sample generator
|
||||
var cliCmd = env.nimCircuitCli
|
||||
for f, v in fieldPairs(args):
|
||||
cliCmd &= " --" & f & "=" & $v
|
||||
|
||||
if not "input.json".fileExists:
|
||||
echo "Generating Circom Files..."
|
||||
runit fmt"{cliCmd} -v --circom={name}.circom --output=input.json"
|
||||
|
||||
proc createCircuit*(
|
||||
args: CircuitArgs,
|
||||
env: CircuitEnv,
|
||||
name = "proof_main",
|
||||
circBenchDir = getCircuitBenchPath(args, env),
|
||||
someEntropy = "some_entropy_75289v3b7rcawcsyiur",
|
||||
doGenerateWitness = false,
|
||||
): tuple[dir: string, name: string] =
|
||||
## Generates all the files needed for to run a proof circuit. Downloads the PTAU file if needed.
|
||||
##
|
||||
## All needed circuit files will be generated as needed.
|
||||
## They will be located in `circBenchDir` which defaults to a folder like:
|
||||
## `nim-codex/benchmarks/circuit_bench_depth32_maxslots256_cellsize2048_blocksize65536_nsamples9_entropy1234567_seed12345_nslots11_ncells512_index3`
|
||||
## with all the given CircuitArgs.
|
||||
##
|
||||
let circdir = circBenchDir
|
||||
|
||||
downloadPtau env.ptauPath, env.ptauUrl
|
||||
|
||||
echo "Creating circuit dir: ", circdir
|
||||
createDir circdir
|
||||
withDir circdir:
|
||||
writeFile("circuit_params.json", pretty(%*args))
|
||||
let
|
||||
inputs = circdir / "input.json"
|
||||
zkey = circdir / fmt"{name}.zkey"
|
||||
wasm = circdir / fmt"{name}.wasm"
|
||||
r1cs = circdir / fmt"{name}.r1cs"
|
||||
wtns = circdir / fmt"{name}.wtns"
|
||||
|
||||
generateCircomAndSamples(args, env, name)
|
||||
|
||||
if not wasm.fileExists or not r1cs.fileExists:
|
||||
runit fmt"circom --r1cs --wasm --O2 -l{env.circuitDirIncludes} {name}.circom"
|
||||
moveFile fmt"{name}_js" / fmt"{name}.wasm", fmt"{name}.wasm"
|
||||
echo "Found wasm: ", wasm
|
||||
echo "Found r1cs: ", r1cs
|
||||
|
||||
if not zkey.fileExists:
|
||||
echo "ZKey not found, generating..."
|
||||
putEnv "NODE_OPTIONS", "--max-old-space-size=8192"
|
||||
if not fmt"{name}_0000.zkey".fileExists:
|
||||
runit fmt"snarkjs groth16 setup {r1cs} {env.ptauPath} {name}_0000.zkey"
|
||||
echo fmt"Generated {name}_0000.zkey"
|
||||
|
||||
let cmd =
|
||||
fmt"snarkjs zkey contribute {name}_0000.zkey {name}_0001.zkey --name='1st Contributor Name'"
|
||||
echo "CMD: ", cmd
|
||||
let cmdRes = execCmdEx(cmd, options = {}, input = someEntropy & "\n")
|
||||
assert cmdRes.exitCode == 0
|
||||
|
||||
moveFile fmt"{name}_0001.zkey", fmt"{name}.zkey"
|
||||
removeFile fmt"{name}_0000.zkey"
|
||||
|
||||
if not wtns.fileExists and doGenerateWitness:
|
||||
runit fmt"node generate_witness.js {wtns} ../input.json ../witness.wtns"
|
||||
|
||||
return (circdir, name)
|
||||
|
||||
when isMainModule:
|
||||
echo "findCodexProjectDir: ", findCodexProjectDir()
|
||||
## test run creating a circuit
|
||||
var env = CircuitEnv.default()
|
||||
env.check()
|
||||
|
||||
let args = CircuitArgs(
|
||||
depth: 32, # maximum depth of the slot tree
|
||||
maxslots: 256, # maximum number of slots
|
||||
cellsize: 2048, # cell size in bytes
|
||||
blocksize: 65536, # block size in bytes
|
||||
nsamples: 5, # number of samples to prove
|
||||
entropy: 1234567, # external randomness
|
||||
seed: 12345, # seed for creating fake data
|
||||
nslots: 11, # number of slots in the dataset
|
||||
index: 3, # which slot we prove (0..NSLOTS-1)
|
||||
ncells: 512, # number of cells in this slot
|
||||
)
|
||||
let benchenv = createCircuit(args, env)
|
||||
echo "\nBench dir:\n", benchenv
|
|
@ -0,0 +1,105 @@
|
|||
import std/[sequtils, strformat, os, options, importutils]
|
||||
import std/[times, os, strutils, terminal]
|
||||
|
||||
import pkg/questionable
|
||||
import pkg/questionable/results
|
||||
import pkg/datastore
|
||||
|
||||
import pkg/codex/[rng, stores, merkletree, codextypes, slots]
|
||||
import pkg/codex/utils/[json, poseidon2digest]
|
||||
import pkg/codex/slots/[builder, sampler/utils, backends/helpers]
|
||||
import pkg/constantine/math/[arithmetic, io/io_bigints, io/io_fields]
|
||||
|
||||
import ./utils
|
||||
import ./create_circuits
|
||||
|
||||
type CircuitFiles* = object
|
||||
r1cs*: string
|
||||
wasm*: string
|
||||
zkey*: string
|
||||
inputs*: string
|
||||
|
||||
proc runArkCircom(args: CircuitArgs, files: CircuitFiles, benchmarkLoops: int) =
|
||||
echo "Loading sample proof..."
|
||||
var
|
||||
inputData = files.inputs.readFile()
|
||||
inputJson = !JsonNode.parse(inputData)
|
||||
proofInputs = Poseidon2Hash.jsonToProofInput(inputJson)
|
||||
circom = CircomCompat.init(
|
||||
files.r1cs,
|
||||
files.wasm,
|
||||
files.zkey,
|
||||
slotDepth = args.depth,
|
||||
numSamples = args.nsamples,
|
||||
)
|
||||
defer:
|
||||
circom.release() # this comes from the rust FFI
|
||||
|
||||
echo "Sample proof loaded..."
|
||||
echo "Proving..."
|
||||
|
||||
let nameArgs = getCircuitBenchStr(args)
|
||||
var proof: CircomProof
|
||||
benchmark fmt"prover-{nameArgs}", benchmarkLoops:
|
||||
proof = circom.prove(proofInputs).tryGet
|
||||
|
||||
var verRes: bool
|
||||
benchmark fmt"verify-{nameArgs}", benchmarkLoops:
|
||||
verRes = circom.verify(proof, proofInputs).tryGet
|
||||
echo "verify result: ", verRes
|
||||
|
||||
proc runRapidSnark(args: CircuitArgs, files: CircuitFiles, benchmarkLoops: int) =
|
||||
# time rapidsnark ${CIRCUIT_MAIN}.zkey witness.wtns proof.json public.json
|
||||
|
||||
echo "generating the witness..."
|
||||
## TODO
|
||||
|
||||
proc runBenchmark(args: CircuitArgs, env: CircuitEnv, benchmarkLoops: int) =
|
||||
## execute benchmarks given a set of args
|
||||
## will create a folder in `benchmarks/circuit_bench_$(args)`
|
||||
##
|
||||
|
||||
let env = createCircuit(args, env)
|
||||
|
||||
## TODO: copy over testcircomcompat proving
|
||||
let files = CircuitFiles(
|
||||
r1cs: env.dir / fmt"{env.name}.r1cs",
|
||||
wasm: env.dir / fmt"{env.name}.wasm",
|
||||
zkey: env.dir / fmt"{env.name}.zkey",
|
||||
inputs: env.dir / fmt"input.json",
|
||||
)
|
||||
|
||||
runArkCircom(args, files, benchmarkLoops)
|
||||
|
||||
proc runAllBenchmarks*() =
|
||||
echo "Running benchmark"
|
||||
# setup()
|
||||
var env = CircuitEnv.default()
|
||||
env.check()
|
||||
|
||||
var args = CircuitArgs(
|
||||
depth: 32, # maximum depth of the slot tree
|
||||
maxslots: 256, # maximum number of slots
|
||||
cellsize: 2048, # cell size in bytes
|
||||
blocksize: 65536, # block size in bytes
|
||||
nsamples: 1, # number of samples to prove
|
||||
entropy: 1234567, # external randomness
|
||||
seed: 12345, # seed for creating fake data
|
||||
nslots: 11, # number of slots in the dataset
|
||||
index: 3, # which slot we prove (0..NSLOTS-1)
|
||||
ncells: 512, # number of cells in this slot
|
||||
)
|
||||
|
||||
let
|
||||
numberSamples = 3
|
||||
benchmarkLoops = 5
|
||||
|
||||
for i in 1 .. numberSamples:
|
||||
args.nsamples = i
|
||||
stdout.styledWriteLine(fgYellow, "\nbenchmarking args: ", $args)
|
||||
runBenchmark(args, env, benchmarkLoops)
|
||||
|
||||
printBenchMarkSummaries()
|
||||
|
||||
when isMainModule:
|
||||
runAllBenchmarks()
|
|
@ -0,0 +1,76 @@
|
|||
import std/tables
|
||||
|
||||
template withDir*(dir: string, blk: untyped) =
|
||||
## set working dir for duration of blk
|
||||
let prev = getCurrentDir()
|
||||
try:
|
||||
setCurrentDir(dir)
|
||||
`blk`
|
||||
finally:
|
||||
setCurrentDir(prev)
|
||||
|
||||
template runit*(cmd: string) =
|
||||
## run shell commands and verify it runs without an error code
|
||||
echo "RUNNING: ", cmd
|
||||
let cmdRes = execShellCmd(cmd)
|
||||
echo "STATUS: ", cmdRes
|
||||
assert cmdRes == 0
|
||||
|
||||
var benchRuns* = newTable[string, tuple[avgTimeSec: float, count: int]]()
|
||||
|
||||
func avg(vals: openArray[float]): float =
|
||||
for v in vals:
|
||||
result += v / vals.len().toFloat()
|
||||
|
||||
template benchmark*(name: untyped, count: int, blk: untyped) =
|
||||
let benchmarkName: string = name
|
||||
## simple benchmarking of a block of code
|
||||
var runs = newSeqOfCap[float](count)
|
||||
for i in 1 .. count:
|
||||
block:
|
||||
let t0 = epochTime()
|
||||
`blk`
|
||||
let elapsed = epochTime() - t0
|
||||
runs.add elapsed
|
||||
|
||||
var elapsedStr = ""
|
||||
for v in runs:
|
||||
elapsedStr &= ", " & v.formatFloat(format = ffDecimal, precision = 3)
|
||||
stdout.styledWriteLine(
|
||||
fgGreen, "CPU Time [", benchmarkName, "] ", "avg(", $count, "): ", elapsedStr, " s"
|
||||
)
|
||||
benchRuns[benchmarkName] = (runs.avg(), count)
|
||||
|
||||
template printBenchMarkSummaries*(printRegular=true, printTsv=true) =
|
||||
if printRegular:
|
||||
echo ""
|
||||
for k, v in benchRuns:
|
||||
echo "Benchmark average run ", v.avgTimeSec, " for ", v.count, " runs ", "for ", k
|
||||
|
||||
if printTsv:
|
||||
echo ""
|
||||
echo "name", "\t", "avgTimeSec", "\t", "count"
|
||||
for k, v in benchRuns:
|
||||
echo k, "\t", v.avgTimeSec, "\t", v.count
|
||||
|
||||
|
||||
import std/math
|
||||
|
||||
func floorLog2*(x: int): int =
|
||||
var k = -1
|
||||
var y = x
|
||||
while (y > 0):
|
||||
k += 1
|
||||
y = y shr 1
|
||||
return k
|
||||
|
||||
func ceilingLog2*(x: int): int =
|
||||
if (x == 0):
|
||||
return -1
|
||||
else:
|
||||
return (floorLog2(x - 1) + 1)
|
||||
|
||||
func checkPowerOfTwo*(x: int, what: string): int =
|
||||
let k = ceilingLog2(x)
|
||||
assert(x == 2 ^ k, ("`" & what & "` is expected to be a power of 2"))
|
||||
return x
|
Loading…
Reference in New Issue