-
Notifications
You must be signed in to change notification settings - Fork 204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Acir size increases exponentially as loop iterations increase, instead of linearly #4629
Comments
I've confirmed that SSA increases linearly by 14 extra instructions each time. The extra instructions are (in order):
(The last iteration of the loop only has 2x add then mul since the last two modify array indices 1 & 2 which are unused in main and are thus eliminated by DIE) So this looks like an Acir-gen issue. |
I've confirmed this is unrelated to array copying in Acir. This makes sense since no arrays are modified. |
For reference, the SSA for a run with 3 iterations looks like this:
|
Doesn't seem like this is the case. Keeping this here for reference however.
|
In the happy case of the initial state being comprised on witnesses only then the number of opcodes required for each iteration should be something like this:
We should have a constant 7 opcodes per iteration My gut feeling is that the issue is due how we're laying down constraints lazily we're accumulating more and more quadratic terms inside This can be seen as if we use the loop below (note the large number of iterations plus the range opcodes being used to enforce that the state is always represented as witnesses) I get a circuit size of 556. Commenting out these range opcodes causes the circuit to grow to 1480.
I also get a constant difference of 11 opcodes if I change the number of loop iterations in this case. Removing 2 for the range opcodes, this doesn't quite line up with the expected 7 but 🤷 |
@TomAFrench Going from 1 to 2 iterations in the original code I see a change of 6 opcodes, which lines up with Zac's original estimate. I also just finished testing acir-gen/ Sounds like we're missing a way to factor out common instances of |
It's kinda yucky as it exposes knowledge about circuits to the end user, but yeah it does seem that a
I don't think so, we're always going to need some laziness as emitting an acir opcode per SSA instruction will mean that we'll never fill up a plonk gate. I'd need to think on if we can identify variables such as |
I've confirmed the extra constraints are being pushed here: https://github.com/noir-lang/noir/blob/master/acvm-repo/acvm/src/compiler/transformers/mod.rs#L85-L96, 3 for the first iter, 4 for the second, then 5, etc. |
…ss to be equal to a variable (#4641) # Description ## Problem\* Quick fix to #4629 ## Summary\* This PR adds a `as_witness` function which takes a `Field` and instructs the compiler to lay down an opcode which constrains a witness to be equal to this value. This prevents the issue in #4629 where we collect ever growing expressions which need to be broken down multiple times while reshaping the circuit. Doing this during acirgen instead allows us to avoid this repetition. ## Additional Context ## Documentation\* Check one: - [ ] No documentation needed. - [ ] Documentation included in this PR. - [x] **[For Experimental Features]** Documentation to be submitted in a separate PR. Feature is experimental # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings.
# Description ## Problem\* Related to #4629 ## Summary\* Do not create a witness when multiplying a witness (or an affine transformation of a witness) with an expression of degree 1. ## Additional Context This PR does NOT fix the issue #4629, but it should help reducing the overall gates number. The arithmetic expressions resulting from the multiplication should not explode in width since I only consider multiplication with a witness (or 'affine witness'). ## Documentation\* Check one: - [X] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [X] I have tested the changes locally. - [X] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings.
@vezenovm do you want to take a look at making ACIR gen aware of the expression width? |
Yeah happy to take this on. |
# Description ## Problem\* Resolves #4629 ## Summary\* This PR updates how we add vars in ACIR gen to account for an expression width. This is under a compile time flag `--bounded-codegen`. If the sum of two expressions is going to go over the specified expression width we automatically create witnesses for either the lhs, the rhs, or both, before then generating a sum expression. This new bounded codegen could provide an easy way for developers to tell whether their program can be optimized using `as_witness`. Reference the additional context section below for some numbers. ## Additional Context There are some limitations in this approach as it is pretty naive in how it decides to when to generate a new witness. It doesn't look ahead at all other than for whether the two AcirVar's being added are going to create an expression over the specified ACIR width. For example we can see the following gate counts for `poseidonsponge_x5_254`: ``` No width awareness: +-----------------------+----------+----------------------+--------------+ | Package | Function | Expression Width | ACIR Opcodes | +-----------------------+----------+----------------------+--------------+ | poseidonsponge_x5_254 | main | Bounded { width: 4 } | 3096 | +-----------------------+----------+----------------------+--------------+ No width awareness w/ as_witness (this is the currently optimized poseidon we have in the stdlib): +-----------------------+----------+----------------------+--------------+ | Package | Function | Expression Width | ACIR Opcodes | +-----------------------+----------+----------------------+--------------+ | poseidonsponge_x5_254 | main | Bounded { width: 4 } | 1302 | +-----------------------+----------+----------------------+--------------+ Width awareness: +-----------------------+----------+----------------------+--------------+ | Package | Function | Expression Width | ACIR Opcodes | +-----------------------+----------+----------------------+--------------+ | poseidonsponge_x5_254 | main | Bounded { width: 4 } | 2114 | +-----------------------+----------+----------------------+--------------+ Width awareness w/ as_witness: +-----------------------+----------+----------------------+--------------+ | Package | Function | Expression Width | ACIR Opcodes | +-----------------------+----------+----------------------+--------------+ | poseidonsponge_x5_254 | main | Bounded { width: 4 } | 1792 | +-----------------------+----------+----------------------+--------------+ ``` From the above we can see that we actually have a degradation when using the addition strategy used in this PR with a hand optimized program using `as_witness`. Although this PR still gives an improvement in the default. Another example is the following program: ```rust fn main(x: Field, y: pub Field) { let state = [x, y]; let state = oh_no_not_again(state); // This assert will fail if we execute assert(state[0] + state[1] == 0); } fn oh_no_not_again(mut state: [Field; 2]) -> [Field; 2] { for _ in 0..200 { state[0] = state[0] * state[0] + state[1]; state[1] += state[0]; } state } ``` Without any width awareness we get 1150 ACIR gates. With this PR we will get 399 gates. If we substitute `oh_no_not_again` for the following: ```rust fn oh_no_not_again_as_witness(mut state: [Field; 2]) -> [Field; 2] { for i in 0..200 { state[0] = state[0] * state[0] + state[1]; std::as_witness(state[0]); state[1] += state[0]; if (i & 1 == 1) { std::as_witness(state[1]); } } state } ``` We will get 301 gates if the method above is called instead of `oh_no_not_again`. ## Documentation\* Check one: - [X] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [X] I have tested the changes locally. - [X] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --------- Co-authored-by: Tom French <[email protected]>
Reopening this since the flag introduced by #5493 is not enabled by default |
Aim
Testing the code in https://github.com/zac-williamson/noir-poseidon1-array-bug.
Expected Behavior
Expected the acir opcode count and backend circuit size to increase by a constant
6
for each additional iteration of the loop inposeidon::bn254::permute
.Bug
Actual change in acir and backend circuit size for iteration total
N
is:To Reproduce
Project Impact
None
Impact Context
No response
Workaround
None
Workaround Description
No response
Additional Context
No response
Installation Method
None
Nargo Version
No response
NoirJS Version
No response
Would you like to submit a PR for this Issue?
None
Support Needs
No response
The text was updated successfully, but these errors were encountered: