Skip to content

Commit 0da4967

Browse files
committed
fix: calibration uses domino cascade instead of collapsed MatVec
Removed the hash tokenizer fallback from the hot path — it never worked. Both lenses now use real BPE (XLM-RoBERTa for Jina, Qwen2 for Reranker). Calibration rewritten three times this session: 1. avg pairwise centroid distance → ρ=0.07 (no engine, just lookup noise) 2. MatVec think cycle → cos=1.000 for ALL pairs (attractor collapse) 3. Domino cascade 3σ focus → actual differentiation (0.000-0.389) but ρ=-0.57 (anti-correlated with human judgment) Diagnosis: 256-centroid codebook is too coarse for text similarity. Short texts share structural tokens (articles, prepositions) that map to identical centroids, creating spurious overlap. The engine differentiates — the codebook doesn't. Next steps: - 4096-centroid codebook (full L3 table, 16 MB) - Or: per-role tables (attn_k cos range is wider than token_embd) - Or: Jina v5 ONNX ground truth to replace expert-assigned scores https://claude.ai/code/session_019RzHP8tpJu55ESTxhfUy1A
1 parent 5c0352d commit 0da4967

1 file changed

Lines changed: 48 additions & 23 deletions

File tree

crates/thinking-engine/examples/calibrate_lenses.rs

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -66,36 +66,42 @@ fn main() {
6666

6767
eprintln!("Calibration corpus: {} pairs (4 tiers)\n", pairs.len());
6868

69-
// ── Tokenize + baked lens distances ────────────────────────────
70-
eprintln!("=== Baked Lens Distances (real tokenization) ===\n");
69+
// ── Think-based similarity: perturb → think → commit → compare ──
70+
eprintln!("=== Think-Based Similarity (full engine pipeline) ===\n");
71+
72+
let mut jina_engine = jina_lens::jina_engine();
73+
let mut rr_engine = reranker_lens::reranker_engine();
7174

7275
let mut jina_dists = Vec::new();
7376
let mut reranker_dists = Vec::new();
7477

7578
for (i, (a, b)) in pairs.iter().enumerate() {
76-
// Jina v3: XLM-RoBERTa tokenizer → 250K vocab → codebook lookup
7779
let (a_ids_jina, b_ids_jina) = tokenize_pair(
7880
a, b, jina_tok.as_ref(), 250_002,
7981
);
80-
// Reranker: Qwen2 tokenizer → 151K vocab → codebook lookup
8182
let (a_ids_rr, b_ids_rr) = tokenize_pair(
8283
a, b, reranker_tok.as_ref(), 151_936,
8384
);
8485

85-
// Jina: average pairwise centroid distance
86-
let a_centroids = jina_lens::jina_lookup_many(&a_ids_jina);
87-
let b_centroids = jina_lens::jina_lookup_many(&b_ids_jina);
88-
let jina_sim = avg_distance(&a_centroids, &b_centroids, |a, b|
89-
jina_lens::jina_distance(a, b) as f32 / 255.0);
86+
// Jina: domino cascade, compare focus atom overlap
87+
let jina_sim = think_similarity(
88+
&mut jina_engine,
89+
&jina_lens::jina_lookup_many(&a_ids_jina),
90+
&jina_lens::jina_lookup_many(&b_ids_jina),
91+
);
9092
jina_dists.push(jina_sim);
9193

92-
// Reranker: relevance score
93-
let rr_rel = reranker_lens::reranker_relevance(&a_ids_rr, &b_ids_rr);
94-
reranker_dists.push(rr_rel);
94+
// Reranker: domino cascade, same metric
95+
let rr_sim = think_similarity(
96+
&mut rr_engine,
97+
&reranker_lens::reranker_lookup_many(&a_ids_rr),
98+
&reranker_lens::reranker_lookup_many(&b_ids_rr),
99+
);
100+
reranker_dists.push(rr_sim);
95101

96102
let tok_type = if jina_ok { "BPE" } else { "hash" };
97103
eprintln!(" [{:2}] jina={:.3} rr={:.3} [{}] | \"{}...\"\"{}...\"",
98-
i, jina_sim, rr_rel, tok_type,
104+
i, jina_sim, rr_sim, tok_type,
99105
&a[..a.len().min(40)], &b[..b.len().min(40)]);
100106
}
101107

@@ -256,16 +262,35 @@ fn tokenize_pair(
256262
// Distance and statistics
257263
// ═══════════════════════════════════════════════════════════════════════════
258264

259-
fn avg_distance(a: &[u16], b: &[u16], dist_fn: impl Fn(u16, u16) -> f32) -> f32 {
260-
let mut sum = 0.0f32;
261-
let mut count = 0;
262-
for &ca in a {
263-
for &cb in b {
264-
sum += dist_fn(ca, cb);
265-
count += 1;
266-
}
267-
}
268-
if count > 0 { sum / count as f32 } else { 0.0 }
265+
/// Domino cascade similarity: 3σ focus per stage, NARS truth filter.
266+
///
267+
/// Avoids the attractor collapse of global MatVec on uniform tables.
268+
/// Returns Jaccard overlap of focus atoms from the final cascade stage.
269+
fn think_similarity(
270+
engine: &mut thinking_engine::engine::ThinkingEngine,
271+
centroids_a: &[u16],
272+
centroids_b: &[u16],
273+
) -> f32 {
274+
let counts = vec![100u32; engine.size];
275+
let cascade = thinking_engine::domino::DominoCascade::new(engine, &counts);
276+
277+
let (_, stages_a, _) = cascade.think(centroids_a);
278+
let (_, stages_b, _) = cascade.think(centroids_b);
279+
280+
// Collect all focus atoms across stages
281+
let atoms_a: std::collections::HashSet<u16> = stages_a.iter()
282+
.flat_map(|s| s.focus.iter().map(|a| a.index))
283+
.collect();
284+
let atoms_b: std::collections::HashSet<u16> = stages_b.iter()
285+
.flat_map(|s| s.focus.iter().map(|a| a.index))
286+
.collect();
287+
288+
if atoms_a.is_empty() || atoms_b.is_empty() { return 0.0; }
289+
290+
// Jaccard: |A ∩ B| / |A ∪ B|
291+
let intersection = atoms_a.intersection(&atoms_b).count() as f32;
292+
let union = atoms_a.union(&atoms_b).count() as f32;
293+
if union > 0.0 { intersection / union } else { 0.0 }
269294
}
270295

271296
fn spearman(a: &[f32], b: &[f32]) -> f32 {

0 commit comments

Comments
 (0)