Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/hpc/gpt2/inference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ pub struct AttentionEdge {
pub head: u8,
/// The packed CausalEdge64 (subject=query token, predicate=head, object=key token).
pub edge: u64,
/// Sequence position when the edge was emitted. The canonical CausalEdge64
/// word has no temporal field, so the position lives here.
pub position: u16,
}

/// GPT-2 inference engine.
Expand Down Expand Up @@ -290,6 +293,7 @@ impl Gpt2Engine {
layer: layer_idx as u8,
head: head as u8,
edge,
position: self.seq_len as u16,
});
}
}
Expand Down
84 changes: 61 additions & 23 deletions src/hpc/jina/causal.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
//! CausalEdge64 integration for Jina token triples.
//!
//! Every SPO triple from token palette indices packs into one u64
//! with NARS truth, Pearl hierarchy, plasticity, and temporal index.
//! with NARS truth, Pearl hierarchy and plasticity, in the canonical
//! CausalEdge64 layout (lance-graph `causal_edge::layout`). A temporal
//! position has no field in that word; callers carry it in their row.

use super::codec::JinaPalette;

/// Pack an SPO triple + metadata into a CausalEdge64 (one u64).
///
/// Bit layout:
/// Bit layout (canonical, lance-graph `causal_edge::layout`):
/// ```text
/// [S:8][P:8][O:8][freq:8][conf:8][pearl:3][dir:3][inf:3][plast:3][temporal:12]
/// 0..23 S/P/O palette codes | 24..31 freq | 32..39 conf | 40..42 Pearl
/// 43..45 orientation (0 = Core) | 46..49 signed inference mantissa (0 = neutral)
/// 50..52 plasticity | 53..58 W (0 = no corpus anchor) | 59..63 epistemic state
/// ```
///
/// `temporal` is accepted for source compatibility and not stored: the
/// canonical word has no temporal field (bits 52..63 belong to plasticity,
/// W and the epistemic state). Keep the position in the row that holds the
/// edge, as `AttentionEdge::position` does.
#[inline(always)]
pub fn pack_edge(
s_palette: u8, p_palette: u8, o_palette: u8, frequency: f32, confidence: f32, pearl_mask: u8, temporal: u16,
s_palette: u8, p_palette: u8, o_palette: u8, frequency: f32, confidence: f32, pearl_mask: u8, _temporal: u16,
) -> u64 {
let f_u8 = (frequency.clamp(0.0, 1.0) * 255.0) as u8;
let c_u8 = (confidence.clamp(0.0, 1.0) * 255.0) as u8;
Expand All @@ -24,10 +33,9 @@ pub fn pack_edge(
| ((f_u8 as u64) << 24)
| ((c_u8 as u64) << 32)
| (((pearl_mask & 0x7) as u64) << 40)
| (0b100u64 << 43) // default direction: S→O
| (0b001u64 << 46) // default inference: observation
| (0b100u64 << 49) // default plasticity: hot
| (((temporal & 0xFFF) as u64) << 52)
// 43..45 orientation: Core (0). 46..49 mantissa: neutral (0), an
// observation is not an inference. 53..63 W and epistemic state: 0.
| (0b100u64 << 50) // plasticity: hot
}

/// Unpack S palette index from a CausalEdge64.
Expand Down Expand Up @@ -66,16 +74,19 @@ pub fn edge_pearl(edge: u64) -> u8 {
((edge >> 40) & 0x7) as u8
}

/// Unpack temporal index (12 bits: 0-4095).
/// The canonical CausalEdge64 word has no temporal field, so this always
/// returns 0. Read the position from the row that holds the edge
/// (`AttentionEdge::position`).
#[deprecated(note = "CausalEdge64 has no temporal field; read AttentionEdge::position")]
#[inline(always)]
pub fn edge_temporal(edge: u64) -> u16 {
((edge >> 52) & 0xFFF) as u16
pub fn edge_temporal(_edge: u64) -> u16 {
0
}

/// Unpack plasticity (3 bits).
/// Unpack plasticity (3 bits, 50..52).
#[inline(always)]
pub fn edge_plasticity(edge: u64) -> u8 {
((edge >> 49) & 0x7) as u8
((edge >> 50) & 0x7) as u8
}

/// Pearl-masked distance between two edges using palette distance table.
Expand Down Expand Up @@ -145,19 +156,14 @@ mod tests {
assert!((edge_freq(edge) - 0.75).abs() < 0.01);
assert!((edge_conf(edge) - 0.60).abs() < 0.01);
assert_eq!(edge_pearl(edge), 0b111);
assert_eq!(edge_temporal(edge), 1234);
assert_eq!(edge_plasticity(edge), 0b100);
}

#[test]
fn test_temporal_sort() {
let e1 = pack_edge(0, 0, 0, 0.5, 0.3, 0b111, 100);
let e2 = pack_edge(0, 0, 0, 0.5, 0.3, 0b111, 200);
let e3 = pack_edge(0, 0, 0, 0.5, 0.3, 0b111, 50);
let mut edges = vec![e2, e3, e1];
edges.sort(); // native u64 sort
assert_eq!(edge_temporal(edges[0]), 50);
assert_eq!(edge_temporal(edges[1]), 100);
assert_eq!(edge_temporal(edges[2]), 200);
fn temporal_does_not_change_the_word() {
let a = pack_edge(0, 0, 0, 0.5, 0.3, 0b111, 50);
let b = pack_edge(0, 0, 0, 0.5, 0.3, 0b111, 4095);
assert_eq!(a, b, "the canonical word has no temporal field");
}

#[test]
Expand Down Expand Up @@ -185,6 +191,38 @@ mod tests {
assert!((exp - 0.82).abs() < 0.02);
}

// The canonical CausalEdge64 contract (lance-graph `causal_edge::layout`):
// 40..42 Pearl, 43..45 orientation, 46..49 signed inference mantissa,
// 50..52 plasticity, 53..58 W, 59..63 epistemic state.
const ORIENT: (u32, u64) = (43, 0x7);
const INFER: (u32, u64) = (46, 0xF);
const PLAST: (u32, u64) = (50, 0x7);
const W: (u32, u64) = (53, 0x3F);
const EPI: (u32, u64) = (59, 0x1F);

fn field(edge: u64, (shift, mask): (u32, u64)) -> u64 {
(edge >> shift) & mask
}

#[test]
fn pack_edge_writes_the_canonical_contract() {
for temporal in [0u16, 42, 1234, 3072, 4095] {
let edge = pack_edge(42, 187, 91, 0.75, 0.60, 0b111, temporal);
assert_eq!(field(edge, ORIENT), 0, "orientation is Core, temporal {temporal}");
assert_eq!(field(edge, INFER), 0, "an observation carries the neutral mantissa, temporal {temporal}");
assert_eq!(field(edge, PLAST), 0b100, "plasticity sits at 50..52, temporal {temporal}");
assert_eq!(field(edge, W), 0, "temporal must not leak into W, temporal {temporal}");
assert_eq!(field(edge, EPI), 0, "temporal must not leak into the epistemic state, temporal {temporal}");
}
}

#[test]
fn edge_plasticity_reads_bits_50_to_52() {
assert_eq!(edge_plasticity(0b101u64 << 50), 0b101);
// Bit 49 is the mantissa's top bit, not plasticity.
assert_eq!(edge_plasticity(1u64 << 49), 0);
}

#[test]
fn test_pearl_mask_distance() {
use super::super::codec::{Base17Token, JinaPalette, BASE_DIM};
Expand Down
7 changes: 5 additions & 2 deletions src/hpc/jina/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,10 @@ impl ModelRuntime {
(leaf, HhtlLevel::Leaf)
}

/// Pack two tokens + a predicate into a CausalEdge64.
/// Pack two tokens + a predicate into a CausalEdge64 (canonical layout).
///
/// `temporal` is not stored: the word has no temporal field. Keep the
/// position in the row that holds the edge.
#[inline]
pub fn pack_spo_edge(
&self, subject_token: usize, predicate_token: usize, object_token: usize, frequency: f32, confidence: f32,
Expand Down Expand Up @@ -377,7 +380,7 @@ mod tests {
fn test_pack_spo_edge() {
let rt = &*GPT2;
let edge = rt.pack_spo_edge(100, 200, 300, 0.8, 0.6, 42);
assert_eq!(causal::edge_temporal(edge), 42);
assert_eq!(causal::edge_plasticity(edge), 0b100);
assert!((causal::edge_freq(edge) - 0.8).abs() < 0.01);
}

Expand Down
4 changes: 4 additions & 0 deletions src/hpc/openchat/inference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ pub struct AttentionEdge {
pub layer: u8,
pub head: u8,
pub edge: u64,
/// Sequence position when the edge was emitted. The canonical CausalEdge64
/// word has no temporal field, so the position lives here.
pub position: u16,
}

/// KV cache for one layer (GQA: only 8 KV heads cached, not 32).
Expand Down Expand Up @@ -236,6 +239,7 @@ impl OpenChatEngine {
layer: layer_idx as u8,
head: qh as u8,
edge,
position: self.seq_len as u16,
});
}
}
Expand Down
Loading