Skip to content

feat(v2.1): switch to checkpointed preflight with rvr - #3074

Draft
shuklaayush wants to merge 196 commits into
develop-v2.1.0from
feat/rvr-preflight
Draft

feat(v2.1): switch to checkpointed preflight with rvr#3074
shuklaayush wants to merge 196 commits into
develop-v2.1.0from
feat/rvr-preflight

Conversation

@shuklaayush

@shuklaayush shuklaayush commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

On develop-v2.1.0, execution and trace generation are coupled. Each instruction is executed by the chip that will later prove it, and that chip appends its own records as it runs. GPU trace generation then turns those records into trace rows:

CPU:  opcode -> chip executor -> per-chip records
GPU:  per-chip records -> that chip's trace rows

This PR splits the old preflight phase into CPU preflight and GPU postflight:

CPU preflight:  opcode execution -> final state + checkpoints + residuals
GPU postflight: checkpoints + residuals -> one shared execution history
GPU tracegen:   the shared history -> system, RV64, and extension traces

CPU preflight remains serial: it executes one instruction at a time against mutable VM memory. With the rvr feature, the compiled executor emits two arrays as it runs, checkpoints taken at a fixed interval and an ordered list of values called residuals, both defined below. GPU postflight re-executes the interval between each pair of checkpoints and builds one shared history that no later stage modifies. The interpreter writes that same history directly. Every trace generator reads it, after execution has finished.

Execution modes

All three modes execute the same opcode implementations and differ only in what they retain:

pure       -> final VM state
metered    -> final VM state + segment boundaries + preflight size bounds
preflight  -> final VM state + the bounded input trace generation needs

Metering chooses the segment boundaries before any proving happens. Preflight executes exactly one of those segments, so the data it retains is bounded by what metering already measured.

How it works

                     program + one bounded segment
                                   |
                                   v
                           serial preflight
                                   |
               +-------------------+-------------------+
               |                                       |
       compiled (`rvr`)                           interpreter
    checkpoints + residuals                  program + memory logs
               |                                       |
               v                                       |
          GPU replay                                   |
     count, allocate, emit                             |
               |                                       |
               +-------------------+-------------------+
                                   |
                                   v
                          postflight indexing
             memory links + touched values + opcode index
                                   |
                                   v
                       shared read-only history
                         program + memory logs
                                   |
               +-------------------+-------------------+
               |                   |                   |
               v                   v                   v
            system               RV64             extensions
               |                   |                   |
               +-------------------+-------------------+
                                   |
                                   v
                           trace generation
                                   |
                                   v
                   release replay and index buffers
                                   |
                                   v
                             STARK proving

The GPU receives a copy of memory before serial execution begins modifying it. Replay and indexing both treat that copy as the value of memory at the start of the segment, and neither writes to it.

What is recorded

  • A checkpoint is a full snapshot of the machine: registers, program counter, logical clock, count of instructions finished, and position in the residual list. The state at the start of the segment serves as the first snapshot.
  • A residual is an ordered value that replay cannot derive on its own, so preflight records it. Replay reconstructs whatever follows from the program, the preceding checkpoint, and the memory image at the start of the segment; a residual supplies what remains. Examples: a value loaded from memory that execution later overwrote, a host hint, a branch outcome inside an extension replay cannot inspect, or the value an extension writes to its output.
  • The program log has one entry per executed instruction, giving its program counter and the logical clock it started at, plus one last entry for where execution ended.
  • The memory log has one event for every register or memory access the proof sees: logical clock, address space, block address, whether it was a read or a write, and the value. Peeks are not events. A peek is a read that execution consults but the proof never observes.
  • The memory index links each logged access to the previous access to the same block, and records what every touched block held at the start and at the end of the segment.
  • The opcode index groups the executed instructions by opcode, so independent trace generators can build their rows in parallel.

A memory-derived residual is what advances replay. The same access also appears as an event in the memory log, and the memory index validates that event against the start-of-segment copy of memory and against the earlier events for that block. The value is therefore consumed by one mechanism and checked by another.

The compiled path's entire output is those two append-only arrays, checkpoints and residuals. Nothing about AIRs or chips appears until postflight has built the shared history, at which point trace generators select the opcodes they own and emit rows.

Postflight and trace generation

Postflight makes two passes over the intervals. The first counts how many events each interval produces, so the output buffers can be allocated at exactly the right size; the second writes the program and memory logs, with the intervals running in parallel. Both backends then build the memory and opcode indexes described above.

This history is where the two execution paths meet. CPU and CUDA build it with separate code, but they check the same things and hand trace generation the same information.

The system, RV64, and extension trace generators locate their instructions through the opcode index and build their rows from four inputs: the instruction as it appears in the program, the program log, the memory log, and the memory index. None of these change while trace generation reads them. Side effects such as reading a host hint occur once, during serial preflight; replay consumes the values they produced and never repeats them. One instruction can feed more than one trace, because executing an opcode is now independent of which chip owns the trace.

The buffers used for replay, sorting, and indexing are released after trace generation and before STARK proving. With them released, proving remains the phase that uses the most GPU memory.

Reuse across proofs

The compiled metering executor, the compiled preflight executor, and the copy of the program on the GPU depend only on the guest program, not on its input. They are prepared once and reused across proofs. Compilation is reported under its own one-time preparation metric, and the per-proof timer starts after preparation.

Each proof then runs:

metering -> preflight -> postflight -> trace generation -> proving

Validation

The default Reth benchmark for block 24001988 completed across 65 segments:

  • preflight: 0.892s;
  • postflight: 1.328s;
  • trace generation: 0.980s;
  • backend STARK proving: 39.367s;
  • peak GPU memory: 15.80 GB.

Everything before backend proving took 4.326s: uploading the starting memory, preflight, postflight, trace generation, and the surrounding bookkeeping.

Resolves INT-8800

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor
group app.proof_time_ms app.cycles leaf.proof_time_ms
fibonacci 476 4,000,051 230
keccak 7,363 14,365,133 1,526
sha2_bench 4,142 11,167,961 520
regex 659 4,090,656 213
ecrecover 231 112,210 182
pairing 240 592,827 185
kitchen_sink 2,052 1,979,971 458

Note: cells_used metrics omitted because CUDA tracegen does not expose unpadded trace heights.

Commit: 150229e

Benchmark Workflow

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants