open-standards/jouleclaw/jouleclaw-rs/crates/jouleclaw-runtime/tests/real_bonsai_streaming.rs
Transaction Science 29dff259e4 jouleclaw Phase 3: backends (reference + apple) + runtime
Three crates ported. All 440 tests pass across Phase 1+2+3.

jouleclaw-backend-reference
  Ported from pattern-lang/joule-backend-reference (~3556 LOC, 17 files).
  Pure-Rust scalar kernels — the determinism oracle every other backend
  validates against. Activation, conv1d, elementwise, lookup (incl. bit
  and ternary), matmul (incl. bit, q8_0, stq1_0, ternary), norm, rope,
  sample, shape, softmax.

jouleclaw-backend-apple
  Ported from pattern-lang/joule-backend-apple (~1116 LOC, 4 files).
  Accelerate (cblas_sgemm via direct FFI; build.rs links the
  framework), Metal compute kernels, MPSMatrixMultiplication. On M5,
  MPS matmul auto-routes through the GPU's Neural Accelerators
  (Tensor APIs). Two Rust-2024 extern-block fixes (unsafe extern "C")
  in accelerate.rs and mps_matmul.rs.

jouleclaw-runtime
  Ported from pattern-lang/joule-runtime (~10218 LOC, 10 files).
  The runtime kernel: chat, embed, generate, streaming, compile,
  execute, validate, plus the speculative-decoding primitives
  (drafter, PLD = prompt lookup decoding). Wires the cascade
  (jouleclaw-cascade), the topology (jouleclaw-topology), the format
  loader (jouleclaw-loader-gguf), and both backends together.

  One Rust-2024 binding-mode fix in compile.rs (|(_, &d)| d == 0 →
  |(_, d)| **d == 0). Three types in jouleclaw-loader-gguf
  (KvQuant, KvSnapshot, ShortConvStateSnapshot) gained
  serde::{Serialize, Deserialize} derives so the runtime's
  ConversationCheckpoint can be serialized; serde added to the
  loader-gguf Cargo.toml.

  Day-one model coverage inherits everything mistral.rs's parent
  loaders support — Llama (incl. Llama 4), Mistral (incl. Small 4),
  Qwen (3, 3-VL, 3 MoE), Gemma (incl. Gemma 4 with MTP drafter), Phi
  (incl. Phi-4 MM), DeepSeek V3, Granite 4 — through this runtime's
  PLD + drafter paths.

  31 integration test files cover: alias-energy, blocks, boot
  validation, causal, chat, chunked prefill, concat-and-offset, embed,
  end-to-end model, generation (incl. inplace), GQA, inplace-alias
  proof, integration, KV cache (incl. inplace), multihead, prefix
  cache, real Bonsai (incl. streaming), real Liquid LFM2, real Tencent
  HYMT, real TinyLlama, repeat, rope-and-scaling, sampling features,
  scatter (incl. inplace), shape, slice (incl. slice-attention),
  streaming, text-to-logits, validation.

Phase 4 next: the model tiers — gemma4, liquid (CfC SSM), prism (1-bit
ternary BitNet), mrl (Matryoshka embeddings), lmm (vision/text/audio),
ebm (energy-based constraint satisfaction). All depend on the runtime
+ backends shipped in this commit.

Deferred to later phases: HAL from efficient-genai + the discrete-GPU
backends (CUDA, ROCm, Vulkan) — these are needed for Phase 5
omni-modal (diffusion, stable-diffusion, gaussian3d, video), not for
Phase 4 text/code/SSM model tiers which run on Apple + reference.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-26 13:09:59 -04:00

85 lines
3.4 KiB
Rust

//! End-to-end streaming oracle: multi-token generation of
//! `Ternary-Bonsai-1.7B-Q2_0` through the actual KV-cache decode path
//! (`Conversation::extend` → `run_inplace_step` →
//! `build_decode_step_graph_inplace` → ternary kernels).
//!
//! `real_bonsai.rs` validates *prefill* only (one forward, one logit
//! row, one greedy token). This validates *generation* — repeated
//! decode steps consuming the cache and the ternary routing inside the
//! in-place block builder. If the decode path's `wmm` / `lookup_w`
//! routing is wrong, the continuation will be incoherent.
//!
//! `#[ignore]` — needs the model on disk. Run:
//!
//! cargo test --release -p jouleclaw-runtime --test real_bonsai_streaming \
//! -- --ignored --nocapture
use jouleclaw_loader_gguf::read_gguf_file;
use jouleclaw_loader_gguf::sample::SamplingConfig;
use jouleclaw_loader_gguf::tokenizer::Vocab;
use jouleclaw_runtime::generate::{GenerateConfig, KvCacheKind, TokenizerKind};
use jouleclaw_runtime::streaming::Conversation;
use jouleclaw_runtime::Runtime;
use std::time::Instant;
fn model_path() -> String {
std::env::var("JOULE_BONSAI")
.unwrap_or_else(|_| "../../models/ternary-bonsai-1.7b-q2_0.gguf".to_string())
}
#[test]
#[ignore]
fn real_bonsai_streams_paris() {
let path = model_path();
eprintln!("loading {}", path);
let t0 = Instant::now();
let model = read_gguf_file(&path).expect("read_gguf_file");
eprintln!(" parsed GGUF in {:?}", t0.elapsed());
let vocab = Vocab::from_gguf(&model).expect("Vocab::from_gguf");
// Reference-only runtime: the AppleAmx f32 drift is a documented
// pre-existing bug; the ternary kernels live only in the reference
// backend anyway, so this is the path that's actually under test.
let mut conv = Conversation::with_runtime(
&model, &vocab, 64, Runtime::reference_only(),
).expect("Conversation::with_runtime");
let cfg = GenerateConfig {
max_new_tokens: 8,
add_bos: false, // qwen2 BPE; no BOS
tokenizer_kind: TokenizerKind::Bpe,
cache_kind: KvCacheKind::InPlace,
sampling: SamplingConfig::greedy(),
max_seq: Some(64),
stop_strings: vec![],
};
let prompt = "The capital of France is";
let t_gen = Instant::now();
let mut tokens: Vec<u32> = Vec::new();
{
let stream = conv.extend(prompt, &cfg).expect("extend");
for st in stream {
let st = st.expect("decode step");
tokens.push(st.id);
}
}
let gen_time = t_gen.elapsed();
let cont = vocab.decode_bpe(&tokens);
eprintln!(" >>> generated {} tokens in {:?} ({:?}/tok)",
tokens.len(), gen_time, gen_time / tokens.len().max(1) as u32);
eprintln!(" >>> tokens = {:?}", tokens);
eprintln!(" >>> {:?}{}", prompt, cont);
// Streaming oracle: greedy generation must include "Paris" early.
// Same bar as the prefill test, applied to the cache-driven path.
assert!(
cont.to_lowercase().contains("paris"),
"STREAMING ORACLE FAILED — got {:?}. Decode/streaming path \
(build_decode_step_graph_inplace + ternary wmm/lookup_w) is \
incorrect.", cont);
let tok_per_s = tokens.len() as f64 / gen_time.as_secs_f64();
eprintln!("VERDICT: Bonsai streaming via in-place KV-cache decode \
produces 'Paris'. Throughput {:.2} tok/s.", tok_per_s);
}