-
Notifications
You must be signed in to change notification settings - Fork 632
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
Map opaque definitions to lossy accumulators in the VM #18917
base: master
Are you sure you want to change the base?
Conversation
Some quick remarks:
|
My point is that, even for reduction, you never want to keep the accumulated terms around. (And just to be clear, my pull request drops accumulated terms only for |
@silene but if you want to go that way, why don't you just write a specific variant of the VM that just doesn't care about open terms? Most things you will ever want to compute are closed booleans anyway. |
Unfortunately no. That was my original impression too, but it cannot be made to work. (I have a branch named The thing is, you are never computing closed booleans. You are always computing |
This is great! I've wanted something like this (although more general than the VM, I've wanted it in terms in general) for a decade.
What are module-sealed definitions considered? I have use-cases that require leaving around sealed opaque constants from VM reduction. As long as this is possible, working around a lack of completeness around Qed-opaque constants should be easy.
How is |
If I understand correctly what you mean, they are currently considered the same as axioms and inductive families: Module Type T. Parameter P : bool -> Prop. End T.
Module M : T.
Definition P : bool -> Prop. Proof. exact (fun b => b = true). Qed.
Fail Eval vm_compute in P false. (* Error: vm_compute reduction has produced an incomplete term. *)
End M.
Eval vm_compute in M.P false. (* = M.P false : Prop *)
It is closed, but this is not what the VM is called on (or any other reduction machinery of Coq). The VM is called on its type, which is Think about it. You always call |
Ah, right. I think the actual confusion on my end, though, was that inductive types (such as |
There is no fundamental reason why we implement inductive types as accumulators. We know the arity of the inductive type statically, we could choose a representation that does not discard the head but treats it as a particular value of type Type. |
First, the only thing an inductive type can do from a computational point of view is to accumulate the arguments that are passed to it. So, calling it an accumulator is just fine. We can tweak the implementation, but it will not fundamentally change what they are. Second, if you mean that we do not need any of the other behaviors associated to accumulators (mostly, constructing |
What I am proposing is an internal change of representation of inductive types in the VM, it shouldn't affect any user-facing code. Basically, rather than implementing it as an accumulator, implement it as a closure of static arity (the length of the context of the inductive, essentially) that returns a block with a specific tag. Just like what the representation of Prod does, but with a different tag, and remembering the underlying inductive. This way you can have lossy accumulators but the VM still works when checking |
First, without non-lossy accumulators, there are breakage all over the place, which is why I gave up on the original Second, a non-lossy accumulator is already a closure. We could add a new construct so that, for inductive types, these closures have a static arity instead of a dynamic one. But I doubt it would cause a noticeable speedup. In fact, the evaluation of the code for non-lossy accumulators ( |
@coqbot run full ci |
They do not aggregate information during computations and thus are fast (no memory allocation, no costly copy). The downside is that the conversion check will fail if any incomplete accumulator remains in the final reduced term.
🔴 CI failures at commit b13ae7f without any failure in the test-suite ✔️ Corresponding jobs for the base commit eccca3a succeeded ❔ Ask me to try to extract minimal test cases that can be added to the test-suite 🏃
|
Regarding the failure in aac-tactics, it happens because |
🔴 CI failures at commit cc5e20f without any failure in the test-suite ✔️ Corresponding jobs for the base commit eccca3a succeeded ❔ Ask me to try to extract minimal test cases that can be added to the test-suite 🏃
|
For Perennial, the calls to (Works val #(U64_val 6) = Works val #(U64_val 6)) which seems nice enough. But if you display the implicit arguments, you get a 1100-line term, which shows that its strong normalization did explode. And you cannot even replace the calls to |
Can we not replace it with a I'm curious about @coqbot CI minimize ci-fiat_crypto |
I am now running minimization at commit cc5e20f on requested target ci-fiat_crypto. I'll come back to you with the results once it's done. |
For MetaCoq, let (x, _) :=
the_loop_terminate 1000000000 [] (NegSpaceClause [Eqv (Var 1) (Var 1)] [] [])
{|
M.this := M.Raw.Node Black M.Raw.Leaf (PureClause [] [] 1 eq_refl) M.Raw.Leaf;
M.is_ok := M.Raw.add_ok M.Raw.Leaf (PureClause [] [] 1 eq_refl)
|} (PureClause [] [] 1 eq_refl) in
x Since |
Indeed, it works. The implicit arguments are still huge, so the strong normalization takes some noticeable time. (About one second to check But that is only if I know beforehand which constants to make transparent. If I try to blindly run Having some |
@Janno pointed out we have code normalizing AVL trees, and those embed Qed-opaque proofs. What then? |
If there is some small fixed set of Qed-opaque proofs, we can just seal them in modules. |
So that there is no misunderstanding, there is an issue only if the final result produced by |
Regarding the Mtac2 failure, as far as I can guess from the code, it seems that the failing tactic creates an identifier using |
|
As far as I can tell, the type seems innocuous. But how is the fresh variable added to the context? My understanding is probably naive (I prefer to stay away from this part of Coq), but I thought that Coq did not let you add an arbitrary name to the context. This name has to be backed up by an actual definition, or at the very least by an evar. |
|
Is |
Minimizer seems to be having some trouble, but it looks like we're just using |
Sorry about the noise, Definition bar :=
let m :=
let w := reduce RedVmCompute (M.bind (M.ret 0) (fun v => M.ret v)) in
w in
ltac:(mrun m). (* Failure, as expected *) But there is something extremely strange about this example, which is why I had a hard time getting rid of Definition bar :=
let m := reduce RedVmCompute (M.bind (M.ret 0) (fun v => M.ret v)) in
ltac:(mrun m). (* Success ??? *) |
I've now had a few minutes to re-familiarize myself with the code. We are looking at a meta-meta-program. Computation on meta-programs is to be expected. As to why I chose |
@JasonGross @silene as a user, why should If the point is just a performance heuristic, it would be more orthogonal to treat it as such: discardability should rather be a tweakable per-constant
That requires duplicating all statements, and moving all relevant clients from one constant to the other. Much more work than tweaking a flag. It's not even local work: if somebody wants to use |
Note that it is not so much the module-opaque constants that are treated differently. It is
No. Or rather it should be clear that such a flag can only impact future definitions (because the previous ones have already been compiled to bytecode), which is presumably not what you expect. As for the local attribute, I am fine with it and I actually considered it. It is presumably a one-liner in
Let me stress again that it only matters if you expect the end result of Inductive tree {elt : Type} :=
| Leaf : tree
| Node : tree -> key -> elt -> tree -> int -> tree. |
Why can't we parameterize the runtime behaviour of accumulators with a flag passed to the VM? This would be the same bytecode, just that it behaves differently depending on the context. Note that we already have a similar mechanism for SProp values in the kernel machine, so as to differentiate conversion (where we erase irrelevant values) from reduction (where we care about the underlying term). |
Hmm... Let me think out loud. So, when the VM executes However, it would prevent something I had on the old branch and that I would have liked to restore. When compiling an application |
Is the size of byte code or the time to generate byte code ever a bottleneck? If not, we can just generate two copies of the byte code for each definition, one to use in lossy mode and one to use in lossless mode. |
Bytecode is already a major contributor to the size of vo files... (Probably for bad reasons, but that'd require fixing it first.) |
Hm, what about wrapping the entire application in some sort of "maybe lossy" wrapper? That is, do the equivalent of Definition maybe_lossy {A} (f : unit -> A) : A := f tt. and compiling |
So, #18927 shows that this criteria is not quite right, as it breaks all the primitive types ( |
Exciting work! On https://xavierleroy.org/publi/extensional-binary-tries.pdf benchmark "repeated", I get that this draft PR already reduces the slowdown from using a sigma type from 5x to 2.6x, and (EDIT) just 1.4x if instead using a dedicated primitive record. |
This pull request introduces a new kind of accumulator that discards its arguments. If this accumulator is still present at the end of the reduction, the conversion check fails with an error message, because we do not have enough information to conclude. This accumulator is used for opaque definitions (i.e.,
Qed
). This obviously breaks completeness when such definitions are not eliminated during computations, but I cannot imagine a case where the user would meaningfully want them to survive a reduction in the VM.Currently, the bytecode compiler does not take advantage of the existence of this accumulator to remove useless computations.
On the following testcase, memory consumption is reduced by 2.2 GB, no major GC is needed, and time is divided by 3.
aac-tactics
: Replace calls to "vm_compute in hyps" by plain calls to vm_compute. coq-community/aac-tactics#138metacoq
: Remove useless calls to Eval vm_compute. MetaCoq/metacoq#1079perennial
: Do not call vm_compute on proof-encumbered terms. mit-pdos/perennial#64