Skip to content

Introduce idle-based eviction for the compiled bundle cache#213

Merged
HudsonGraeme merged 1 commit into
mainfrom
introduce/bounded-bundle-cache
Jul 4, 2026
Merged

Introduce idle-based eviction for the compiled bundle cache#213
HudsonGraeme merged 1 commit into
mainfrom
introduce/bounded-bundle-cache

Conversation

@HudsonGraeme

@HudsonGraeme HudsonGraeme commented Jul 4, 2026

Copy link
Copy Markdown
Member

The compiled bundle cache retained every circuit ever loaded for the process lifetime, which on long-running verifiers grows toward the full circuit corpus and pins host memory. Measurement of production verification traffic shows bundle reuse is a burst phenomenon: 97 percent of reuse occurs within sixty seconds of the prior touch, driven by tiles of the same run verifying together, after which a bundle goes cold. Direct reads from the backing store reload the largest bundles in a fifth of a second and the median bundle in single-digit milliseconds, so retention beyond the burst window buys nothing.

Cache entries now carry a last-touched timestamp refreshed on hit, and an eviction method drops entries idle beyond a caller-supplied duration. Callers that sweep periodically retain the measured hit rate while steady-state cache size tracks the actively verifying working set instead of the corpus.

Summary by CodeRabbit

  • New Features

    • Added a new example tool for inspecting a single witness and printing input/output statistics.
    • Example input normalization can now be adjusted with an environment variable instead of being fixed.
  • Bug Fixes

    • Improved cache behavior so recently used circuit bundles stay available longer, while stale entries can now be cleared.
  • Tests

    • Updated coverage for the new cache eviction behavior.

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR adds a new witness_single example for computing per-slice witness statistics, adjusts witness_group's input normalization via a NORM_DIVISOR environment variable, and adds idle-based eviction to JstproveBackend's bundle cache by tracking per-entry touched timestamps.

Changes

Witness Example Normalization

Layer / File(s) Summary
Normalize witness_group input
crates/dsperse/examples/witness_group.rs
Input tensor is now divided by an optional NORM_DIVISOR env variable (default 1.0) instead of using a fixed constant.
Add witness_single example
crates/dsperse/examples/witness_single.rs
New CLI example loads model metadata, runs CombinedRun for a slice, normalizes inputs, computes a witness via JstproveBackend, and prints input/output statistics.

Bundle Cache Idle Eviction

Layer / File(s) Summary
Cache timestamp tracking and eviction
crates/dsperse/src/backend/jstprove.rs
bundle_cache values now store (Arc<CompiledCircuit>, Instant), load_bundle_cached refreshes/inserts touched times, a new evict_idle(ttl) method removes stale entries, and tests are updated accordingly.

Estimated code review effort: 2 (Simple) | ~12 minutes

Possibly related PRs

  • inference-labs-inc/dsperse#64: Both PRs modify JstproveBackend's bundle-caching mechanism, changing the cache's stored value structure and eviction behavior.
  • inference-labs-inc/dsperse#101: Both PRs modify bundle_cache storage and load_bundle_cached logic, with the main PR extending the cache introduced there with timestamps and evict_idle.
  • inference-labs-inc/dsperse#147: The new witness_single.rs example calls into CombinedRun pipeline APIs introduced in this PR.

Poem

A rabbit hops with norms in tow,
Dividing inputs, watching them glow,
A cache now ticks with timestamps neat,
Stale bundles swept from the burrow's seat,
Witness stats printed, hop hop, complete! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding idle-based eviction to the compiled bundle cache.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch introduce/bounded-bundle-cache

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/dsperse/src/backend/jstprove.rs (1)

528-532: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test doesn't actually verify "only stale entries" are dropped.

The test name evict_idle_drops_only_stale_entries implies mixed stale/fresh entries are exercised, but the body only checks evict_idle on an empty cache, which is a trivial 0-result case already covered conceptually by the method's retain no-op behavior. Consider inserting one entry with an already-elapsed synthetic timestamp (stale) and one freshly-inserted entry (via load_bundle_cached or direct insert with Instant::now()), then asserting the stale entry is evicted while the fresh one survives.

✅ Suggested stronger test
     #[test]
     fn evict_idle_drops_only_stale_entries() {
         let backend = JstproveBackend::default();
         assert_eq!(backend.evict_idle(std::time::Duration::from_secs(0)), 0);
+
+        let dummy = Arc::new(CompiledCircuit {
+            circuit: vec![1],
+            witness_solver: vec![],
+            metadata: None,
+            version: None,
+        });
+        {
+            let mut cache = backend.bundle_cache.lock().unwrap();
+            // Stale entry: touched far enough in the past to be older than ttl.
+            let stale_touch = std::time::Instant::now() - std::time::Duration::from_secs(120);
+            cache.insert(PathBuf::from("/tmp/stale"), (Arc::clone(&dummy), stale_touch));
+            // Fresh entry: touched now.
+            cache.insert(PathBuf::from("/tmp/fresh"), (dummy, std::time::Instant::now()));
+        }
+        let evicted = backend.evict_idle(std::time::Duration::from_secs(60));
+        assert_eq!(evicted, 1);
+        let cache = backend.bundle_cache.lock().unwrap();
+        assert_eq!(cache.len(), 1);
+        assert!(cache.contains_key(Path::new("/tmp/fresh")));
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/dsperse/src/backend/jstprove.rs` around lines 528 - 532, The test
named evict_idle_drops_only_stale_entries is too trivial and does not exercise
mixed stale and fresh cache entries. Update the JstproveBackend test to set up
one stale entry and one fresh entry, using load_bundle_cached or direct
insertion with an Instant::now()-based timestamp, then call evict_idle and
assert only the stale entry is removed while the fresh one remains. Keep the
test focused on JstproveBackend::evict_idle and the cache state before and after
eviction.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/dsperse/src/backend/jstprove.rs`:
- Around line 65-74: The evict_idle method on jstprove’s bundle cache is
implemented but never scheduled, so idle entries are never actually removed.
Wire JstProve::evict_idle into the existing runtime/background sweep path by
adding a periodic caller in the component that owns the cache and choosing a TTL
consistent with bundle lifetime, so cache cleanup runs automatically instead of
relying on manual invocation.

---

Nitpick comments:
In `@crates/dsperse/src/backend/jstprove.rs`:
- Around line 528-532: The test named evict_idle_drops_only_stale_entries is too
trivial and does not exercise mixed stale and fresh cache entries. Update the
JstproveBackend test to set up one stale entry and one fresh entry, using
load_bundle_cached or direct insertion with an Instant::now()-based timestamp,
then call evict_idle and assert only the stale entry is removed while the fresh
one remains. Keep the test focused on JstproveBackend::evict_idle and the cache
state before and after eviction.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f798d44f-2ab0-452f-9d27-d1d4bee3cf4f

📥 Commits

Reviewing files that changed from the base of the PR and between c746ea7 and 1a74c9c.

📒 Files selected for processing (3)
  • crates/dsperse/examples/witness_group.rs
  • crates/dsperse/examples/witness_single.rs
  • crates/dsperse/src/backend/jstprove.rs

Comment on lines +65 to +74
pub fn evict_idle(&self, ttl: std::time::Duration) -> usize {
let mut cache = match self.bundle_cache.lock() {
Ok(cache) => cache,
Err(e) => e.into_inner(),
};
let now = std::time::Instant::now();
let before = cache.len();
cache.retain(|_, (_, touched)| now.duration_since(*touched) < ttl);
before - cache.len()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Search the repo for any caller that periodically invokes evict_idle.
rg -n -C3 '\bevict_idle\s*\(' --type=rust

Repository: inference-labs-inc/dsperse

Length of output: 164


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Files mentioning evict_idle ==\n'
rg -n -C 2 '\bevict_idle\s*\(' --type=rust || true

printf '\n== Files mentioning bundle_cache ==\n'
rg -n -C 2 '\bbundle_cache\b' crates/dsperse/src --type=rust || true

printf '\n== Files mentioning sweep/periodic/timer/spawn in dsperse src ==\n'
rg -n -C 2 '\b(sweep|periodic|timer|interval|spawn|timeout)\b' crates/dsperse/src --type=rust || true

Repository: inference-labs-inc/dsperse

Length of output: 199


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Rust files in crates/dsperse/src ==\n'
git ls-files 'crates/dsperse/src/**/*.rs' 'crates/dsperse/src/*.rs'

printf '\n== Search for cache eviction / sweep related symbols ==\n'
rg -n -C 2 '\b(evict|sweep|ttl|idle|cache)\b' crates/dsperse/src --type=rust || true

Repository: inference-labs-inc/dsperse

Length of output: 23693


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Search repo-wide for background-task patterns ==\n'
rg -n -C 2 '\b(tokio::spawn|spawn\(|interval\(|sleep\(|spawn_blocking|Recurring|cron|scheduler)\b' . --type=rust || true

printf '\n== Search repo-wide for eviction / sweep names ==\n'
rg -n -C 2 '\b(evict_idle|sweep|sweeper|cleanup|prune)\b' . --type=rust || true

Repository: inference-labs-inc/dsperse

Length of output: 4446


Wire evict_idle into a periodic sweep.
I couldn’t find any caller or scheduler using this method, so the bundle cache still has no idle eviction path and can keep growing unbounded.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/dsperse/src/backend/jstprove.rs` around lines 65 - 74, The evict_idle
method on jstprove’s bundle cache is implemented but never scheduled, so idle
entries are never actually removed. Wire JstProve::evict_idle into the existing
runtime/background sweep path by adding a periodic caller in the component that
owns the cache and choosing a TTL consistent with bundle lifetime, so cache
cleanup runs automatically instead of relying on manual invocation.

@HudsonGraeme HudsonGraeme merged commit 7bfc349 into main Jul 4, 2026
12 checks passed
@HudsonGraeme HudsonGraeme deleted the introduce/bounded-bundle-cache branch July 4, 2026 18:38
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.

1 participant