Snapshot Restore for Coding-Agent MicroVMs: Working-Set Prefetch, Diff Snapshots, and Tiered Caches
Coding agents require a full computer that installs packages, builds, and runs tests. Reinforcement learning (RL) on coding tasks consumes thousands of fresh environments an hour, each used once and discarded, so platforms boot once, install the toolchain, warm every cache, and save the entire machine; every later environment is a restore.
This note describes how Firecracker snapshots work and the techniques that make restoring them fast at this scale. A lazy restore resumes the guest in milliseconds but moves the cost into page faults during the first second of execution; most of the techniques below avoid paying for those faults one at a time.
- Working-set prefetch. Restores of one snapshot touch largely the same pages, so a recorded set can be read sequentially. With a 100 µs fault, a page touched in more than about 2% of restores is worth prefetching.
- Diff snapshots. Saving only dirty pages arranges snapshots into a tree, so forking a running environment for best-of-N sampling or tree search costs one diff.
- Tiered chunk caches. Content-addressed chunks in memory, local NVMe, peers, and object storage keep the slowest tier off the critical path; peers reach a thousand hosts in 5 rounds.
- Huge pages. A 2 MiB page is cheaper to restore than its 512 small pages once about a dozen are touched.
Snapshot Format
Firecracker is the virtual machine monitor that AWS built for Lambda. Snapshotting a paused microVM writes two files: a small vmstate file containing vCPU registers and device state, and a memory file containing every page of guest RAM. A restore loads both into a fresh Firecracker process, and the guest continues from the instruction at which it was paused.
The disk is not part of the snapshot. The guest’s block device must be exactly as it was at the moment of the snapshot; otherwise the guest’s in-memory view of its filesystem no longer matches the contents of the disk. In practice the disk is a copy-on-write layer over a read-only image, frozen together with the memory file.
Lazy Restore
With the default File backend, Firecracker maps the memory file privately into the new process. Nothing is read in advance: a page is loaded when the guest first touches it, and a write produces a private copy. A 16 GiB guest therefore resumes in milliseconds, and many guests restored from one file on one host share its untouched pages through the host page cache. Lazy restore with the File backend. Untouched pages are shared through the host page cache; written pages become private copies.
The cost moves to the first second after resume. The guest touches its working set immediately, each first touch is a page fault, and each fault is a small random read. Against local NVMe this is tolerable; against remote storage it dominates.
Working-Set Prefetch
Restores of the same snapshot touch mostly the same pages in mostly the same order, because the guest resumes the same code from the same state. The access pattern can therefore be recorded once and replayed.
The UFFD backend delivers every page fault to a user-space handler through Linux’s userfaultfd. On a first restore, the handler logs which pages were faulted and in what order. On later restores, it copies that set into guest memory in one sequential pass, before or alongside resume, and falls back to on-demand faults only for pages outside the set. REAP (ASPLOS 2021) measured this approach on Firecracker and reduced cold-start time severalfold; FaaSnap (EuroSys 2022) improved on it by loading the set concurrently with execution rather than before it.REAP is part of the vHive project. Both papers found that the working set is small relative to guest memory and stable across invocations, which is what makes recording it worthwhile.
Let be the set of pages a restore touches. A lazy restore pays one fault for each:
A prefetching restore reads the recorded set in one pass at sequential bandwidth , with page size , and faults only on the pages it missed:
When a fault is a round trip to remote storage, the term dominates, and prefetching replaces it with a single large read.
A recorded set is a prediction. If page is touched with probability across restores, prefetching it costs with certainty and saves with probability . To first order, the page should be prefetched exactly when
With 4 KiB pages, 2 GB/s of sequential bandwidth, and a 100 µs fault, the threshold is about 2%. A page touched in one restore out of twenty is still worth reading ahead, and the more expensive a fault, the more inclusive the set should be.
The working set also determines the storage layout: recorded pages should be written contiguously, so that the prefetch is one large read rather than thousands of small ones.
Snapshot Size Reduction
A memory file is the size of guest RAM, most of which is free or holds data that could be regenerated. Three measures keep it small:
Bytes stored per snapshot for a 16 GiB guest after each reduction. The last step trades restore speed for size. Illustrative.
- Inflate a balloon before snapshotting. The guest hands its free pages to the balloon driver, and they become zero pages in the file.
- Store the file as content-addressed chunks. Zero chunks, and chunks identical across snapshots such as an unchanged kernel or a shared toolchain, are stored once.
- Decide deliberately whether to keep the guest’s page cache. Dropping it shrinks the snapshot, but keeping it is often the reason for snapshotting, since a warm compiler cache or loaded model weights are what make the restored machine fast.
Diff Snapshots
With dirty page tracking enabled, KVM records every page the guest writes, and a diff snapshot saves only those pages. A diff cannot be used alone; it is applied over the snapshot from which it was taken, and Firecracker’s snapshot-editor can merge the two.Tracking dirty pages adds a small cost to every first write to a page, so it is disabled unless requested.
Diffs arrange snapshots into a tree: a base containing the operating system, a child containing the toolchain, and below that a child with a specific repository checked out at a specific commit. Each level shares everything above it.
This is where coding-agent platforms differ most from serverless workloads. An RL rollout can snapshot an environment mid-trajectory and restore it N times to explore N continuations, and best-of-N sampling and tree search over a coding agent’s actions require the same operation. Branching a running computer becomes as cheap as writing a diff, so the environment, and not only the model’s context, can be forked.
If the guest makes page writes spread uniformly over pages, the expected number of distinct dirty pages is
A diff grows almost linearly at first and then saturates toward all of memory, so a diff taken soon after its parent is small and one taken much later is nearly a full snapshot. Real writes concentrate in the heap and page cache, which makes diffs smaller than this model predicts, but the shape holds: snapshots should be taken often, or rebased onto a new full snapshot.
A tree stores each node once. Its total size is over every diff in it, compared with for independent full snapshots.
(a) Diff size under uniform and skewed writes. (b) Storage for a tree of diffs against independent full snapshots. Illustrative.
Tiered Chunk Caches
Snapshots are stored in tiers, from fastest to most durable: pages already in a host’s page cache, chunks on local NVMe, and object storage. AWS’s 2023 paper on loading Lambda container images describes the design most platforms now follow: split images into chunks named by content hash, deduplicate across customers, and place a shared cache tier between hosts and S3.
A single fault on a restored guest takes the following path. KVM traps the access and userfaultfd delivers it to the handler, which maps the page to the chunk containing it, ensures that only one fetch for that chunk is in flight, and queries the tiers in order until one holds it. The data is copied into guest memory with UFFDIO_COPY. In parallel, the prefetcher streams the recorded working set from local disk, so that most pages never fault.
With hit rates and for the first two tiers and latencies , the expected cost of a fault is
Object storage is orders of magnitude slower than local NVMe, so even a small share of faults reaching it determines the average. The objective is therefore less a high hit rate than keeping the last tier off the critical path.
(a) Fetch latency per tier. (b) Expected fault cost as the share of faults reaching object storage grows, with a first-tier hit rate of 0.9. Illustrative.
Two problems are specific to coding agents. The first is the thundering herd: an RL job starts, and a thousand hosts fault on the same new snapshot within seconds. Without coordination, hosts restoring one snapshot with a working set of chunks send requests to object storage at once. A regional cache that coalesces identical misses into one fetch sends . Peer distribution performs better still: if every host holding a chunk serves it to others, the number of hosts holding it multiplies by each round, and all hold it after
rounds. A thousand hosts with are covered in 5 rounds. Pushing hot chunks before the job begins removes even those rounds from the critical path.
The second is that a UFFD handler can fetch from any tier, so a restore can begin before the snapshot has finished downloading. The guest runs on the working set while the remaining pages stream in behind it.
Huge Pages
Backing guest memory with 2 MiB huge pages reduces translation lookaside buffer (TLB) misses, and each fault brings in 512 times as much memory. For restores this is a trade-off: far fewer faults, but each is larger and loads memory the guest may never use.
Consider a 2 MiB region in which the guest touches of its 512 small pages. With 4 KiB pages the restore pays about ; with one huge page it pays . Huge pages are cheaper when
which, with the parameters above, is about a dozen pages. Dense regions such as the heap favor huge pages, and sparse regions do not.
(a) Restore cost per region; 4 KiB pages become more expensive above about 11.5 touched pages. (b) Pages touched per region at restore. Illustrative. Firecracker supports huge-page guests, and restoring them requires the UFFD backend.
Limitations
A snapshot captures a frozen CPU. A guest that observed a set of CPU features at boot can crash if it resumes on a host without them, so fleets with mixed hardware use CPU templates to mask features to a common set. Snapshots are also tied to the Firecracker version that wrote them.
A snapshot also captures frozen secrets. Every environment restored from one base starts with the same random number generator state, machine ID, and any keys held in memory. Linux’s VM generation ID driver reseeds the kernel’s generator when the hypervisor signals a restore, and AWS documents the user-space side for SnapStart, but any state that drew randomness before the snapshot must still be found and refreshed. For RL this has two consequences: identical state is a uniqueness defect for networking and a benefit for reproducible rollouts.