mem_evolve automatically evolves Linux page-cache eviction policies using
LLM-driven code generation. Policies are written as eBPF programs against the
cache_ext kernel (a custom 6.6.8
build from the SOSP 2025 cache_ext paper). A Python coordinator mutates
policies across parallel search branches, evaluates each candidate on a
cgroup-isolated benchmark, and feeds kernel-level probe measurements back to the
LLM so the next mutation is informed by real performance data.
MemEvolve: Evolving Page Cache Policies with LLMs Dhruv Desai, Yashwanth Ranjan Singaravel, Michael Swift University of Wisconsin–Madison 📄 MemEvolve_Thesis.pdf
The Linux page cache relies on general-purpose, mostly LRU-derived eviction
strategies that are not tuned to any particular workload. MemEvolve is an offline
framework that uses an LLM to generate candidate eviction policies directly as
native eBPF code, searches over them with breadth-first evolution, and deploys
the winners in a running kernel through cache_ext — so evolved policies run in
a real kernel, not a simulator. The evolution loop follows the
iterative-refinement-with-execution-feedback pattern (Self-Refine, Reflexion,
Voyager): each candidate's kernel-level probe feedback — cgroup refault counts,
I/O byte counts, and policy-declared counters — is fed back into the next round's
prompt so the model refines its proposals rather than guessing blindly. The
central finding is that specialization, not universality, is where LLM-driven
policy synthesis earns its keep: policies evolved for a specific workload
consistently beat the general-purpose baselines cache_ext ships with.
Key ideas:
- Native-code policy synthesis with real deployment — the LLM writes eBPF,
and winners load into a live kernel via
cache_ext(no rebuild, no simulator). - Kernel-probe feedback channel — cgroup-level refault counts, I/O bytes, and counters dumped on exit are threaded back into the next round's prompt as the gradient that drives mutation.
- Three-role split on different timescales — a planner (runs once, seeds K branches), a mutator (hot path, one focused edit per branch per round), and a frontier reviewer (periodically reshapes the population: continue / kill / pivot / spawn), with per-branch chat-history compaction to keep prompts bounded.
- Lightweight distributed evaluator — one branch is pinned to one host per round to keep cross-cgroup cache interference out of the score, making the inner loop tractable in wall-clock time on a small cluster.
- Three case studies — a synthetic adversarial workload (
scan_thrash), a real production trace (twitter_leveldb_ro), and a novel LLM-driven workload (agent_swarm); the policy-counter feedback channel is what lets the search converge when headroom is small.
evolve.py → evolution.loop.evolve(cfg)
├── planner picks K seed policies (LRU, FIFO, S3-FIFO, …) + a focus hint each
├── each seed becomes the root of a search branch
└── for each round, in parallel across branches:
├── mutator LLM proposes a new policy from the branch's history + last
│ round's probe feedback
├── the policy is compiled (BPF + userspace loader) and run under a
│ benchmark cgroup on a worker node
├── probes read kernel counters (refaults, bytes read, wallclock, …)
└── results are recorded in the evolution tree
every few rounds a "frontier" reviewer reshapes branches
(continue / kill / pivot / spawn).
Each candidate is measured with workload-agnostic probes that read kernel counters rather than parsing benchmark output:
| Probe | Signal |
|---|---|
WallclockProbe |
total runtime |
CgroupIostatProbe |
rbytes — bytes the workload had to read from disk |
CgroupMemstatProbe |
workingset_refault_file — direct refault count |
PolicyCountersProbe |
optional counters a policy dumps on exit |
cache_policy_evolution/— the coordinator (Python).evolve.py— CLI entry point; loads a TOML config and runs the loop.evaluator/— standalone policy evaluator: compile a policy, run it under a cgroup, collect probe results. Usable as a library orpython3 -m evaluator.evaluate.evolution/— the parallel branch search: evolution tree, per-branch LLM chat history, frontier reviewer, seed planner, and the mainevolve()loop.worker_server.py— stdlib HTTP daemon that runs on each worker node and evaluates compiled policies sent by the coordinator.targets/— compilation helpers (split combined.cinto BPF + loader).seeds/— seed policies:fifo.c,lru.c,mru.c,s3_fifo.c,scan_resist.c,noop.c.eval/— benchmark setup + run scripts (scan-thrash, filebench, YCSB/RocksDB, Twitter/LevelDB).examples/— small self-contained demos.*.toml— example run configs.
evolution_analyzer/— single-file script that renders an evolution-tree JSON into a self-contained interactive HTML page.claude_api/— a small OpenAI-compatible proxy backed by the Claude Code CLI, so the coordinator's OpenAI client can talk to Claude Code locally. Optional/alternative to a LiteLLM proxy — the default configs point at LiteLLM instead (see Quickstart).- CloudLab provisioning helpers —
setup_cloudlab.sh,setup_main_node.sh,start_workers.sh,parse_ssh.sh. cache_ext/— the custom kernel + eBPF runtime, pulled in as a git submodule.
git clone --recurse-submodules <this-repo>
cd evo_cache/cache_policy_evolution
pip install -r requirements.txt
# LLM calls go through a LiteLLM proxy (OpenAI-compatible) by default — see
# [llm.mutator]/[llm.planner] in the TOMLs (api_base, api_key_env). Point
# api_base at your LiteLLM server and export the key it reads:
export LITELLM_MASTER_KEY=... # or whatever api_key_env names in your TOML
# Run locally (workers = [] in the config → single in-process worker):
python3 evolve.py scan_thrash.toml
python3 evolve.py scan_thrash.toml --rounds 30
python3 evolve.py scan_thrash.toml --resumeTo distribute evaluation across nodes, launch a worker daemon on each host and list their URLs in the config:
# on each worker:
python3 worker_server.py --port 8080# in your TOML:
workers = ["http://host1:8080", "http://host2:8080"]- Must run on the custom
6.6.8-cache-extkernel (eBPFcache_extstruct_ops). See thecache_extsubmodule for build/install scripts. - System deps:
clang-14,bpftool,libbpf,build-essential,libelf-dev. - Python deps: see
cache_policy_evolution/requirements.txt. - An LLM endpoint reachable from the coordinator — a LiteLLM proxy by default
(any OpenAI-compatible endpoint works via
llm_adapter; direct Anthropic access is also supported by settingprovider = "anthropic"in the TOML).
The coordinator needs clang/bpftool (it compiles policies and ships
pre-built binaries to workers). Worker nodes only need Python 3, the booted
custom kernel, and the benchmark cgroup set up.
See CLAUDE.md for a deeper architecture reference.