Five crates ported / designed. All 235 tests pass; 0 errors;
0 warnings outside the loader-gguf NEON-intrinsics module (suppressed
with a documented #![allow(unsafe_op_in_unsafe_fn)] — proper fix is
wrapping each intrinsic call in unsafe { }, tracked as kernel-quality
maintenance, not standards work).
jouleclaw-cascade
Ported from pattern-lang/joule-cascade (~7044 LOC, 17 files).
L0 (Cache), tier dispatch, 7-axis Synthesis coordinate router, cost
model, calibration ledger (in-memory + disk), history, verification,
body + body_safety policy gates, active-entity tracking, l2_service
trait surface. Two tests that depended on joule-l1's lawful-primitive
impl (which requires pattern-lang's pattern-core + cortex-ir +
synthesizer — OpenIE IP) were removed; the L1 trait surface lives
here and consumers plug their own lawful lexicon in.
jouleclaw-loader-gguf
Ported from pattern-lang/joule-loader-gguf (~11605 LOC, 17 files).
GGUF v3 parser with mmap, Llama and Gemma / Gemma 4 decoders,
Q4_0 / Q5_K / Q8_0 / Q8_0 grouped quants, in-place KV cache,
matmul_q8_0 with NEON intrinsics on Apple Silicon, safetensors
reader, tokenizer + Gemma tokenizer, sample, dequant.
jouleclaw-pack (NEW)
The .jc.toml sidecar — JouleClaw's standard-defining contribution.
A model on disk (GGUF / safetensors / MLX) tells you tensor shapes
and quant schemes. It does NOT tell you what one forward pass
actually costs in joules on real hardware. The .jc.toml is a
declared-cost contract that travels with the model:
- per-tensor precision (FP16/BF16/FP8/INT8/NVFP4/NF4/Q4_K/...)
- page-alignment status (mmap-zero-copy possible? on which UMA?)
- measured J/op on a reference hardware matrix with explicit
Provenance (HwShunt / ModelBased / Estimator)
- drift_factor tolerance per measurement; the runtime trips
PackError::CostDrift when measured > declared * drift_factor
and down-weights the backend in subsequent auction rounds
Without a declared-cost contract, "energy-optimized inference" is
marketing. With one, it is an engineering claim any third party
can verify against the published reference-hardware corpus.
5 lib tests pass. ModelFormat, Precision, Alignment, MeasurementOp,
Pack, TensorContract, Measurement, hash_model(), verify_measurement().
jouleclaw-history
Ported from pattern-lang/joule-history (~2026 LOC, 6 files).
Persistent resolution history: disk + in-memory + tiered
(hot/warm/cold) + semantic. One Rust-2024 edition fix in
tiered.rs (binding-mode pattern: |(_, &seq)| seq -> |(_, seq)| *seq).
jouleclaw-wire
Ported from pattern-lang/joule-wire (~1129 LOC, 2 files).
L4 wire tier — the remote frontier escape hatch. RPC client +
transport, invoked only when the local cascade returns Unresolvable.
Phase 3 next: HAL from efficient-genai, backend-{apple,reference,
cuda,metal,rocm,vulkan}, runtime (port joule-runtime — chat/embed/
generate/streaming/PLD/drafter — needs backends to compile).
Co-Authored-By: Claude Opus 4.7 <[email protected]>
342 lines
12 KiB
Rust
342 lines
12 KiB
Rust
//! R8 tests — tiered memory transitions.
|
|
//!
|
|
//! Proves:
|
|
//! - Hot tier respects capacity; excess items demote to Warm
|
|
//! - Warm tier respects capacity; excess items demote to Cold
|
|
//! - Lookups walk Hot → Warm → Cold
|
|
//! - Cold/Warm hits promote back to Hot
|
|
//! - Transition joule costs are recorded and bounded
|
|
|
|
use jouleclaw_cascade::*;
|
|
use jouleclaw_history::*;
|
|
|
|
fn text(s: &str) -> Query {
|
|
Query {
|
|
input: QueryInput::Text(s.to_string()),
|
|
budget: JouleBudget::standard(),
|
|
quality: QualityFloor::any(),
|
|
context: ContextRef::fresh(),
|
|
deadline: None,
|
|
}
|
|
}
|
|
|
|
fn answer(text: &str, tier: TierId, joules: f64) -> Answer {
|
|
Answer {
|
|
output: AnswerOutput::Text(text.to_string()),
|
|
tier_used: tier,
|
|
joules_spent: joules,
|
|
confidence: 0.9,
|
|
trace: ExecutionTrace::default(),
|
|
verification: jouleclaw_cascade::verification::VerificationStatus::Resolved,
|
|
}
|
|
}
|
|
|
|
struct TempPath { path: std::path::PathBuf }
|
|
impl TempPath {
|
|
fn new(label: &str) -> Self {
|
|
let nanos = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos();
|
|
Self {
|
|
path: std::env::temp_dir().join(format!(
|
|
"joule-r8-test-{}-{}-{}.log",
|
|
label, std::process::id(), nanos)),
|
|
}
|
|
}
|
|
fn p(&self) -> &std::path::Path { &self.path }
|
|
}
|
|
impl Drop for TempPath {
|
|
fn drop(&mut self) { let _ = std::fs::remove_file(&self.path); }
|
|
}
|
|
|
|
// ============================================================
|
|
// WarmCache unit tests
|
|
// ============================================================
|
|
|
|
#[test]
|
|
fn warm_cache_respects_capacity() {
|
|
let mut warm = WarmCache::new(3);
|
|
for i in 0..3 {
|
|
let q = text(&format!("query-{}", i));
|
|
let a = answer(&format!("answer-{}", i), TierId::L4(L4ModelId(0)), 0.5);
|
|
warm.record(&q, &a).unwrap();
|
|
}
|
|
assert_eq!(warm.len(), 3);
|
|
|
|
// Insert a 4th item — should evict the LRU (query-0).
|
|
let q = text("query-3");
|
|
let a = answer("answer-3", TierId::L4(L4ModelId(0)), 0.5);
|
|
warm.record(&q, &a).unwrap();
|
|
assert_eq!(warm.len(), 3, "capacity should hold at 3");
|
|
|
|
// query-0 should be gone.
|
|
let key_0 = key_for(&text("query-0"));
|
|
assert!(warm.lookup_exact(&key_0).unwrap().is_none());
|
|
|
|
// query-3 should be there.
|
|
let key_3 = key_for(&text("query-3"));
|
|
assert!(warm.lookup_exact(&key_3).unwrap().is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn warm_cache_touches_on_lookup() {
|
|
let mut warm = WarmCache::new(3);
|
|
for i in 0..3 {
|
|
let q = text(&format!("q-{}", i));
|
|
let a = answer(&format!("a-{}", i), TierId::L4(L4ModelId(0)), 0.5);
|
|
warm.record(&q, &a).unwrap();
|
|
}
|
|
|
|
// Touch q-0 — should move it to MRU.
|
|
let key_0 = key_for(&text("q-0"));
|
|
warm.lookup_exact(&key_0).unwrap();
|
|
|
|
// Insert q-3 — q-1 should be evicted now (not q-0).
|
|
let q = text("q-3");
|
|
let a = answer("a-3", TierId::L4(L4ModelId(0)), 0.5);
|
|
warm.record(&q, &a).unwrap();
|
|
|
|
assert!(warm.lookup_exact(&key_0).unwrap().is_some(), "q-0 should survive (touched)");
|
|
let key_1 = key_for(&text("q-1"));
|
|
assert!(warm.lookup_exact(&key_1).unwrap().is_none(), "q-1 should have been evicted");
|
|
}
|
|
|
|
// ============================================================
|
|
// TieredMemory integration
|
|
// ============================================================
|
|
|
|
#[test]
|
|
fn tiered_memory_fills_hot_first() {
|
|
let t = TempPath::new("hot_first");
|
|
let mut m = TieredMemory::open(2, 4, t.p()).unwrap();
|
|
|
|
// Insert two items — both should land in Hot.
|
|
for i in 0..2 {
|
|
let q = text(&format!("q-{}", i));
|
|
let a = answer(&format!("a-{}", i), TierId::L4(L4ModelId(0)), 0.5);
|
|
m.record(&q, &a).unwrap();
|
|
}
|
|
let (hot, warm, _) = m.tier_sizes();
|
|
assert_eq!(hot, 2);
|
|
assert_eq!(warm, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn tiered_memory_demotes_overflow_from_hot_to_warm() {
|
|
let t = TempPath::new("demote_hw");
|
|
let mut m = TieredMemory::open(2, 4, t.p()).unwrap();
|
|
|
|
// Insert 4 items into a 2-capacity Hot. Two should fall through
|
|
// to Warm.
|
|
for i in 0..4 {
|
|
let q = text(&format!("q-{}", i));
|
|
let a = answer(&format!("a-{}", i), TierId::L4(L4ModelId(0)), 0.5);
|
|
m.record(&q, &a).unwrap();
|
|
// Sleep 1ms-ish to ensure distinct timestamps for LRU.
|
|
|
|
}
|
|
let (hot, warm, cold) = m.tier_sizes();
|
|
assert_eq!(hot, 2, "Hot should hold 2 items");
|
|
assert_eq!(warm, 2, "Warm should hold the 2 demoted items");
|
|
assert_eq!(cold, 4, "Cold (durable) should hold all 4");
|
|
}
|
|
|
|
#[test]
|
|
fn tiered_memory_lookup_walks_hot_warm_cold() {
|
|
let t = TempPath::new("walk");
|
|
let mut m = TieredMemory::open(2, 2, t.p()).unwrap();
|
|
|
|
// Insert 6 items into a 2/2/inf tiered memory. Final state:
|
|
// Hot: q-4, q-5 (latest 2)
|
|
// Warm: q-2, q-3 (next 2 most recent)
|
|
// Cold: all 6
|
|
for i in 0..6 {
|
|
let q = text(&format!("q-{}", i));
|
|
let a = answer(&format!("a-{}", i), TierId::L4(L4ModelId(0)), 0.5);
|
|
m.record(&q, &a).unwrap();
|
|
|
|
}
|
|
|
|
// Lookup q-5 — should hit Hot.
|
|
m.lookup_exact(&key_for(&text("q-5"))).unwrap();
|
|
assert_eq!(m.last_hit_tier, Some(MemoryTier::Hot));
|
|
|
|
// Lookup q-0 — should hit Cold (it's old, fell out of Hot and Warm).
|
|
m.lookup_exact(&key_for(&text("q-0"))).unwrap();
|
|
assert_eq!(m.last_hit_tier, Some(MemoryTier::Cold),
|
|
"q-0 should be in Cold only; got {:?}", m.last_hit_tier);
|
|
}
|
|
|
|
#[test]
|
|
fn tiered_memory_promotes_on_cold_hit() {
|
|
let t = TempPath::new("promote");
|
|
let mut m = TieredMemory::open(2, 2, t.p()).unwrap();
|
|
|
|
// Fill hierarchy beyond Hot+Warm capacity.
|
|
for i in 0..6 {
|
|
let q = text(&format!("q-{}", i));
|
|
let a = answer(&format!("a-{}", i), TierId::L4(L4ModelId(0)), 0.5);
|
|
m.record(&q, &a).unwrap();
|
|
|
|
}
|
|
|
|
// Lookup q-0 — Cold hit.
|
|
let k0 = key_for(&text("q-0"));
|
|
let r1 = m.lookup_exact(&k0).unwrap();
|
|
assert!(r1.is_some());
|
|
assert_eq!(m.last_hit_tier, Some(MemoryTier::Cold));
|
|
|
|
// Lookup q-0 again — should now be in Hot (promoted on first access).
|
|
let r2 = m.lookup_exact(&k0).unwrap();
|
|
assert!(r2.is_some());
|
|
assert_eq!(m.last_hit_tier, Some(MemoryTier::Hot),
|
|
"after promotion q-0 should be Hot; got {:?}", m.last_hit_tier);
|
|
}
|
|
|
|
#[test]
|
|
fn tiered_memory_records_transition_joules() {
|
|
let t = TempPath::new("joules");
|
|
let mut m = TieredMemory::open(1, 1, t.p()).unwrap();
|
|
|
|
// Fill enough to force transitions.
|
|
for i in 0..3 {
|
|
let q = text(&format!("q-{}", i));
|
|
let a = answer(&format!("a-{}", i), TierId::L4(L4ModelId(0)), 0.5);
|
|
m.record(&q, &a).unwrap();
|
|
|
|
}
|
|
|
|
// Lookup the oldest — Cold hit, promotion to Hot.
|
|
let _ = m.lookup_exact(&key_for(&text("q-0"))).unwrap();
|
|
|
|
// total_transition_joules should be nonzero and small.
|
|
assert!(m.total_transition_joules > 0.0);
|
|
assert!(m.total_transition_joules < 1e-3,
|
|
"transition cost should be small; got {:.3e}", m.total_transition_joules);
|
|
}
|
|
|
|
#[test]
|
|
fn tiered_memory_miss_returns_none() {
|
|
let t = TempPath::new("miss");
|
|
let mut m = TieredMemory::open(2, 2, t.p()).unwrap();
|
|
let result = m.lookup_exact(&key_for(&text("never recorded"))).unwrap();
|
|
assert!(result.is_none());
|
|
assert_eq!(m.last_hit_tier, None);
|
|
}
|
|
|
|
// ============================================================
|
|
// Runtime integration: tiered memory plugged into the cascade
|
|
// ============================================================
|
|
|
|
struct MockL4 { cost: f64, response: String }
|
|
impl Tier for MockL4 {
|
|
fn id(&self) -> TierId { TierId::L4(L4ModelId(0)) }
|
|
fn estimate_cost(&self, _q: &Query) -> Option<TierEstimate> {
|
|
Some(TierEstimate {
|
|
joules: self.cost,
|
|
latency: std::time::Duration::from_millis(100),
|
|
confidence_floor: 0.9,
|
|
})
|
|
}
|
|
fn try_answer(&mut self, _q: &Query, _b: f64) -> Result<Answer, AnswerError> {
|
|
Ok(Answer {
|
|
output: AnswerOutput::Text(self.response.clone()),
|
|
tier_used: self.id(),
|
|
joules_spent: self.cost,
|
|
confidence: 0.9,
|
|
trace: ExecutionTrace::default(),
|
|
verification: jouleclaw_cascade::verification::VerificationStatus::Resolved,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn runtime_with_tiered_memory_hits_l0_on_repeat_query() {
|
|
let t = TempPath::new("runtime");
|
|
let memory = Box::new(TieredMemory::open(2, 2, t.p()).unwrap());
|
|
|
|
let mut cascade = Cascade::new();
|
|
cascade.register(Box::new(MockL4 {
|
|
cost: 0.5, response: "Paris".into(),
|
|
}));
|
|
let mut rt = Runtime::new_with_history(cascade, memory);
|
|
|
|
let q = text("capital of France?");
|
|
let a1 = rt.answer(q.clone()).unwrap();
|
|
assert_eq!(a1.tier_used, TierId::L4(L4ModelId(0)));
|
|
|
|
let a2 = rt.answer(q).unwrap();
|
|
assert_eq!(a2.tier_used, TierId::L0,
|
|
"second query should hit L0 (via tiered memory)");
|
|
assert!(a2.joules_spent < 1e-5,
|
|
"L0 hit should be cheap; got {:.3e}", a2.joules_spent);
|
|
}
|
|
|
|
#[test]
|
|
fn runtime_query_falls_through_tiers_on_repeat() {
|
|
// Spam more queries than fit in Hot+Warm. Old ones must be
|
|
// recoverable from Cold via the L0 path.
|
|
let t = TempPath::new("falls_through");
|
|
let memory = Box::new(TieredMemory::open(2, 2, t.p()).unwrap());
|
|
|
|
let mut cascade = Cascade::new();
|
|
cascade.register(Box::new(MockL4 {
|
|
cost: 0.5, response: "answer".into(),
|
|
}));
|
|
let mut rt = Runtime::new_with_history(cascade, memory);
|
|
|
|
// Send 10 distinct queries; only 4 can fit in Hot+Warm.
|
|
for i in 0..10 {
|
|
rt.answer(text(&format!("query-{}", i))).unwrap();
|
|
}
|
|
// The first query should still be retrievable via Cold.
|
|
let a = rt.answer(text("query-0")).unwrap();
|
|
assert_eq!(a.tier_used, TierId::L0,
|
|
"old query should still hit L0 via Cold tier; got {:?}", a.tier_used);
|
|
assert_eq!(a.output, AnswerOutput::Text("answer".to_string()));
|
|
}
|
|
|
|
// ============================================================
|
|
// Demo
|
|
// ============================================================
|
|
|
|
#[test]
|
|
fn r8_tiered_memory_demo() {
|
|
println!("\n=== R8: tiered memory (Hot/Warm/Cold) with joule-priced transitions ===\n");
|
|
|
|
let t = TempPath::new("demo");
|
|
let mut m = TieredMemory::open(2, 2, t.p()).unwrap();
|
|
|
|
println!("Memory hierarchy: Hot=2, Warm=2, Cold=∞ (disk)\n");
|
|
|
|
// Insert 6 items, observing the hierarchy.
|
|
for i in 0..6 {
|
|
let q = text(&format!("query-{}", i));
|
|
let a = answer(&format!("answer-{}", i), TierId::L4(L4ModelId(0)), 0.5);
|
|
m.record(&q, &a).unwrap();
|
|
|
|
}
|
|
let (hot, warm, cold) = m.tier_sizes();
|
|
println!("After 6 records:");
|
|
println!(" Hot: {} items (most recent)", hot);
|
|
println!(" Warm: {} items (recently demoted)", warm);
|
|
println!(" Cold: {} items (durable on disk)", cold);
|
|
println!(" Total transition joules so far: {:.3e} J", m.total_transition_joules);
|
|
|
|
println!("\nLookups walk Hot → Warm → Cold and promote on hit:\n");
|
|
for i in [5, 3, 0] {
|
|
let key = key_for(&text(&format!("query-{}", i)));
|
|
let _ = m.lookup_exact(&key).unwrap();
|
|
println!(" query-{}: hit {:?}", i, m.last_hit_tier.unwrap());
|
|
}
|
|
println!();
|
|
println!("After looking up query-0 (Cold hit):");
|
|
let (hot, warm, cold) = m.tier_sizes();
|
|
println!(" Hot: {} items (query-0 promoted here)", hot);
|
|
println!(" Warm: {} items", warm);
|
|
println!(" Cold: {} items", cold);
|
|
println!(" Total transition joules: {:.3e} J", m.total_transition_joules);
|
|
|
|
println!("\nKey point: hot data costs ~6 nJ; cold data costs ~1 µJ;");
|
|
println!("but a single promotion lifts a hot-accessed item back to Hot,");
|
|
println!("so repeated access converges to Hot cost.");
|
|
}
|