Repository navigation
Conversation
Adds an opt-in config field "parallel_replay" (default false). When it is set, comparator replays the solution with Lean.Kernel.Environment.replayParallel, defined in Comparator/Replay.lean: a copy of Lean's src/Lean/Replay.lean (v4.35.0-rc3) in which each theorem is added with addDeclWithoutChecking and checked by addDeclCore, against the environment just before it, in a Task. All tasks are awaited, and any kernel error fails the replay, before the postponed constructor/recursor checks. Non-theorem declarations are checked in order as before. Tests: tests/ParallelReplayUnit.lean (same verdict as replay on valid and forged inputs, run in CI) and the integration tests parallel_match and parallel_olean_issue. Closes leanprover#95.
| This file is a copy of `src/Lean/Replay.lean` from Lean v4.35.0-rc3, renamed to | ||
| `Lean.Kernel.Environment.replayParallel` (namespace `ParallelReplay`) so that it does not clash with | ||
| Lean's own `replay`. The only change in behavior: theorems are added to the environment with |
There was a problem hiding this comment.
Do you need to copy the whole file? The more you can use by importing the previous file the better, since that reduces the size of the TCB.
There was a problem hiding this comment.
good point, thanks! pushed a refactor: it now imports Lean.Replay and reuses Context, State, M, isTodo, addDecl, throwKernelException and the postponed ctor/rec checks. the only copied code left is replayConstant (its .thmInfo case is the actual change) and the loop in replayParallel. one catch: those helpers aren't public in core, so this uses import all Lean.Replay. if you'd rather avoid that, happy to send a small lean4 PR that makes them public (or adds a hook for the theorem case) so comparator doesn't need to copy anything.
| structure State where | ||
| env : Kernel.Environment | ||
| remaining : NameSet := {} | ||
| pending : NameSet := {} | ||
| postponedConstructors : NameSet := {} | ||
| postponedRecursors : NameSet := {} | ||
| /-- Kernel checks of theorems, running in parallel; see `addThmAsync`. -/ | ||
| tasks : Array (Name × Task (Except Kernel.Exception Kernel.Environment)) := #[] |
There was a problem hiding this comment.
For instance
| structure State where | |
| env : Kernel.Environment | |
| remaining : NameSet := {} | |
| pending : NameSet := {} | |
| postponedConstructors : NameSet := {} | |
| postponedRecursors : NameSet := {} | |
| /-- Kernel checks of theorems, running in parallel; see `addThmAsync`. -/ | |
| tasks : Array (Name × Task (Except Kernel.Exception Kernel.Environment)) := #[] | |
| structure State extends Replay.State where | |
| /-- Kernel checks of theorems, running in parallel; see `addThmAsync`. -/ | |
| tasks : Array (Name × Task (Except Kernel.Exception Kernel.Environment)) := #[] |
There was a problem hiding this comment.
went with your ExtraState version instead, since stacking it on Replay.M lets the core helpers be reused as is.
| /-- Kernel checks of theorems, running in parallel; see `addThmAsync`. -/ | ||
| tasks : Array (Name × Task (Except Kernel.Exception Kernel.Environment)) := #[] | ||
|
|
||
| abbrev M := ReaderT Context <| StateRefT State IO |
There was a problem hiding this comment.
Maybe the answer here to minimize the repetition is
structure ExtraState where
tasks : Array (Name × Task (Except Kernel.Exception Kernel.Environment)) := #[]
abbrev M := StateRefT ExtraState (Replay.M)There was a problem hiding this comment.
done, thanks: abbrev M := StateRefT ExtraState Replay.M.
There was a problem hiding this comment.
This doesn't seem to be what you have? Did you change your mind?
There was a problem hiding this comment.
yes, sorry, i should have said: i changed it in the latest push to keep the copied code verbatim. with StateRefT ExtraState Replay.M the outer state is ExtraState, so every get/modify in the copied replayConstant had to become getThe Replay.State/modifyThe Replay.State (that's what 1164baa did). putting the task list in the reader context instead (structure Context extends Replay.Context with an IO.Ref) keeps get, modify and read meaning what they mean in lean's code, so the copy is identical except the theorem case, and a MonadLift Replay.M M instance runs the Lean.Replay helpers unchanged. happy to switch back if you prefer the ExtraState version.
Per review: Comparator/Replay.lean now imports Lean.Replay (`import all`, since its helpers are not public there) and reuses Context, State, M, isTodo, throwKernelException, addDecl and the postponed constructor and recursor checks. The theorem-check tasks are kept in an ExtraState stacked on top of Replay.M (`StateRefT ExtraState Replay.M`). The only code still copied from Lean v4.35.0-rc3 is replayConstant, whose .thmInfo case is the actual change (addThmAsync instead of addDecl), and the loop in replayParallel, which awaits the theorem checks before the postponed constructor and recursor checks.
|
If you don't want to wait for us to decide if we want the official kernel replay to be mutl-threaded, you can
(I'm actually tweaking con-leche right now to reduce the serial part further, including parallizing the JSON parsing, precisely to make huge proofs like yours bearable.) |
|
thanks joachim, that's really helpful. happy to leave #99 open until you decide, no rush. in the meantime we'll try nanoda and con-leche on our export. is there a supported way to run comparator with only the external kernels (right now it always runs the builtin replay too), or should we run them directly on the lean4export output? and if it's useful for your con-leche work, our export is a decent stress test: ~105 GB (lean 4.33.1), 22.1M constants, 9.65M theorems, mostly decide-heavy certificates. the builtin replay took 83 h with 342 threads. happy to share timings. |
Doesn’t look like it, unfortunately, but I think it is a reasonable request. Happy to learn how con-leche fares on your huge export, and give you a sneak preview on the more parallel version, but let’s continue this on zulip maybe? |
|
thanks! con-leche accepted the whole export once we raised its loop step budgets (the official build runs out of fuel on one big |
…yConstant and replay This commit copies replayConstant, replayConstants and replay from Lean's src/Lean/Replay.lean (identical in v4.35.0-rc3, v4.35.0-rc4 and master) verbatim into namespace Lean.Kernel.Environment.Replay.Parallel, with M := Replay.M, so replayParallel temporarily behaves exactly like replay. The next commit is then the complete parallel change, as a diff against Lean's own code.
The complete parallel change, as a diff against the verbatim copy of Lean's replayConstant and replay in the previous commit: - the .thmInfo case of replayConstant calls addThmAsync instead of addDecl: the theorem is added with addDeclWithoutChecking and checked by the same kernel call (addDeclCore) against the environment just before it, in a Task; - the reader context extends Replay.Context with the list of these tasks (the Lean.Replay helpers are lifted into the new monad unchanged); - replay creates the list and awaits every task before the postponed constructor/recursor checks, failing if any task failed.
|
@eric-wieser thanks, that makes sense. two changes:
i also merged master (rc4) into the branch. |
Closes #95.
Adds an opt-in config field
parallel_replay(defaultfalse). When it is set, comparator replays the solution withLean.Kernel.Environment.replayParallel, defined inComparator/Replay.lean. That file importsLean.Replay(viaimport all) and contains copies of Lean'sreplayConstant,replayConstantsandreplay, with three small changes:replayConstantadds the theorem withaddDeclWithoutCheckingand checks it withaddDeclCore, against the environment just before it, in aTask;replayawaits them.Reviewing. Commit 431631c copies those three definitions verbatim from Lean's
src/Lean/Replay.lean(identical in v4.35.0-rc3, v4.35.0-rc4 and master). So commit b005862 shows the complete change against Lean's own code. As suggested in review, the same change is proposed for Lean core in leanprover/lean4#15569; if it lands there, this file can go away.All tasks are awaited before the postponed constructor/recursor checks, and any kernel error fails the replay. Non-theorem declarations are checked in order as before, and the default sequential path is unchanged.
Tests:
tests/ParallelReplayUnit.leanchecks that the verdict matchesreplayon valid and forged inputs. It is added to CI.parallel_matchandparallel_olean_issue.Motivation: the export of https://github.com/dpwoodru/general-courtade-kumar-lean is ~100 GB, with 22.1M constants, mostly
decideproofs of generated certificates. A single-threaded replay was projected at one to two weeks. A build of this approach for Lean 4.33.1 replayed it in 83 h with 342 threads on one machine and accepted the solution. Details are inverification/comparator/results/2026-10-04/of that repository.This change was drafted with AI coding assistance.