Five tier crates ported. 589 tests pass across Phase 1-4, zero failures, zero warnings. jouleclaw-liquid Ported from pattern-lang/liquid (~1123 LOC, 5 files). L3 tier — Closed-form Continuous-time (CfC) recurrent language models. The SSM class with O(L) state evolution vs transformer O(L²) attention. This is the v0.3 "SSM bet" from the architecture proposal — already shipping in the JouleClaw reference impl on day one. jouleclaw-prism Ported from pattern-lang/prism (~3066 LOC, 6 files). L3 tier — 1-bit / ternary BitNet-class kernels. Ternary transformer forward pass. The lowest-joule local-model tier. Provides the TernaryDecoder backbone that jouleclaw-lmm builds on. jouleclaw-mrl Ported from pattern-lang/mrl (~1464 LOC, 7 files). L2 tier — Matryoshka Representation Learning nested embeddings. Sub-millijoule retrieval tier. Embedder + GGUF embedder + matryoshka truncation + nearest-neighbor picker + retrieval. jouleclaw-lmm Ported from pattern-lang/lmm (~1819 LOC, 6 files). L3 tier — Large Multimodal Model. Vision + text + audio pipeline on top of jouleclaw-prism's TernaryDecoder backbone. Real VL path (LFM2.5-VL preprocessing via the `image` crate, JPEG/PNG only — keeps the dep footprint minimal). The internal `prism::` references rewritten to `jouleclaw_prism::` during port. jouleclaw-ebm Ported from pattern-lang/ebm (~1738 LOC, 7 files). Energy-based constraint-satisfaction tier — SAT, n-queens, sudoku, graph coloring, generic solver. Deterministic compute priced in joules, sits at the L1/L3 boundary depending on the problem class. Bare crate-name references in integration tests fixed: `use liquid::...` → `use jouleclaw_liquid::...` (and same for the other 4 ported tiers). Initial sed pass only caught `joule_*` patterns; this second pass caught the bare-name ones. gemma4 deferred. It depends on `joule-l1` and `pattern-core` (OpenIE IP that doesn't port). Will land in Phase 6 once the thin `LawfulTier` trait surface ships in jouleclaw-cascade or a new jouleclaw-l1 crate so consumers can plug their own lawful lexicon in. Phase 5 next: omni-modal generation — diffusion (40+ samplers from efficient-genai's ALGORITHM_CATALOG), Stable Diffusion / SDXL / SD3 / Flux paths, MusicGen, Whisper, Gaussian3D, video synthesis, cross-modal fusion. Source codebase: efficient-genai. Co-Authored-By: Claude Opus 4.7 <[email protected]>
101 lines
4.3 KiB
Rust
101 lines
4.3 KiB
Rust
//! End-to-end MRL oracle for **jinaai/jina-embeddings-v5-omni-nano**.
|
|
//!
|
|
//! Validates everything that fired for the first time on this model:
|
|
//!
|
|
//! 1. **`eurobert`** — fifth supported architecture (encoder, not
|
|
//! decoder). Different metadata prefix, different output contract.
|
|
//! 2. **Bidirectional attention** — `<arch>.attention.causal = False`
|
|
//! drives the non-causal branch of `build_block`, which uses
|
|
//! `g.softmax` instead of `g.softmax_causal`.
|
|
//! 3. **`build_llama_encoder_graph`** terminates at `hidden_states`
|
|
//! (no LM head), exposing the post-final-norm activations.
|
|
//! 4. **`GgufTextEmbedder`** wraps that with BPE tokenisation, last-
|
|
//! token pooling (LFM-style `pooling_type=3`), optional Matryoshka
|
|
//! truncation, and L2 normalisation. Lands the previously-empty
|
|
//! real-encoder seat in `crates/mrl`.
|
|
//!
|
|
//! Oracle gate: encode two related sentences + one unrelated, check
|
|
//! that the related pair has higher cosine similarity than either to
|
|
//! the unrelated one. This is the substrate-level "the embedding is
|
|
//! doing something meaningful" smoke test — much stronger than
|
|
//! "logits finite". For Matryoshka the same ordering must hold at a
|
|
//! truncated dimension too.
|
|
|
|
use jouleclaw_mrl::GgufTextEmbedder;
|
|
use std::time::Instant;
|
|
|
|
fn model_path() -> String {
|
|
std::env::var("JOULE_JINA_V5")
|
|
.unwrap_or_else(|_|
|
|
"../../models/jina-v5-omni-nano-retrieval-q4_k_m.gguf".to_string())
|
|
}
|
|
|
|
fn cosine(a: &[f32], b: &[f32]) -> f32 {
|
|
assert_eq!(a.len(), b.len());
|
|
a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
|
|
// Inputs are already L2-normalised by GgufTextEmbedder.
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn real_jina_v5_embeds_meaningfully() {
|
|
let path = model_path();
|
|
eprintln!("loading {}", path);
|
|
let t0 = Instant::now();
|
|
let emb = GgufTextEmbedder::from_gguf(&path).expect("from_gguf");
|
|
eprintln!(" loaded in {:?}", t0.elapsed());
|
|
eprintln!(" arch={} full_dim={} pooling={:?}",
|
|
emb.arch(), emb.full_dim(), emb.pooling());
|
|
assert_eq!(emb.arch(), "eurobert");
|
|
let d = emb.full_dim();
|
|
|
|
// Three sentences: the first two are about Paris, the third is about
|
|
// food. A meaningful encoder should make cos(s1,s2) > cos(s1,s3).
|
|
let s1 = "The capital of France is Paris.";
|
|
let s2 = "Paris is the largest city in France.";
|
|
let s3 = "Pizza was invented in Naples, Italy.";
|
|
|
|
let t1 = Instant::now();
|
|
let v1 = emb.encode(s1, None).expect("encode s1");
|
|
let v2 = emb.encode(s2, None).expect("encode s2");
|
|
let v3 = emb.encode(s3, None).expect("encode s3");
|
|
eprintln!(" encoded 3 sentences in {:?} (full d={})", t1.elapsed(), d);
|
|
assert_eq!(v1.len(), d);
|
|
assert_eq!(v2.len(), d);
|
|
assert_eq!(v3.len(), d);
|
|
|
|
// Sanity: every vector is finite + approximately unit-norm.
|
|
for (lbl, v) in [("s1", &v1), ("s2", &v2), ("s3", &v3)] {
|
|
let n2: f32 = v.iter().map(|x| x * x).sum();
|
|
assert!(v.iter().all(|x| x.is_finite()), "{} not finite", lbl);
|
|
assert!((n2 - 1.0).abs() < 1e-3, "{} not unit-norm: n²={}", lbl, n2);
|
|
}
|
|
|
|
let sim_12 = cosine(&v1, &v2);
|
|
let sim_13 = cosine(&v1, &v3);
|
|
let sim_23 = cosine(&v2, &v3);
|
|
eprintln!(" cos(Paris1, Paris2) = {:.4}", sim_12);
|
|
eprintln!(" cos(Paris1, pizza) = {:.4}", sim_13);
|
|
eprintln!(" cos(Paris2, pizza) = {:.4}", sim_23);
|
|
assert!(sim_12 > sim_13,
|
|
"ORACLE FAILED: related Paris pair must out-similar the food sentence \
|
|
(got {:.4} ≤ {:.4})", sim_12, sim_13);
|
|
assert!(sim_12 > sim_23,
|
|
"ORACLE FAILED: related Paris pair must out-similar Paris-vs-food \
|
|
(got {:.4} ≤ {:.4})", sim_12, sim_23);
|
|
|
|
// Matryoshka: truncate to 64 dims. Same ordering must hold (any
|
|
// prefix is a valid lower-fidelity embedding).
|
|
let v1t = emb.encode(s1, Some(64)).unwrap();
|
|
let v2t = emb.encode(s2, Some(64)).unwrap();
|
|
let v3t = emb.encode(s3, Some(64)).unwrap();
|
|
let sim_12_t = cosine(&v1t, &v2t);
|
|
let sim_13_t = cosine(&v1t, &v3t);
|
|
eprintln!(" matryoshka d=64: cos(P1,P2)={:.4} cos(P1,pizza)={:.4}",
|
|
sim_12_t, sim_13_t);
|
|
assert!(sim_12_t > sim_13_t,
|
|
"MATRYOSHKA ORACLE FAILED at d=64: {:.4} ≤ {:.4}", sim_12_t, sim_13_t);
|
|
|
|
eprintln!("VERDICT: jina-embeddings-v5-omni-nano (eurobert, bidirectional, \
|
|
last-pool, Q4_K_M) embeds meaningfully at full dim {} and at d=64.", d);
|
|
}
|