feat(dbt+ai): build out cubedbt and cubeai packages (Package 4, PDF §549/§551)
cubedbt: DBT runtime that reads CZYX-stored translation rules and patches them into a code cache to execute mimicked behavior. TranslationRule (CZYX Variant record in c220 band) maps an op-class to a replacement bytecode fragment; CodeCache patches an original CodeCell under eligible rules and writes the result as a Variant (preserving behavior descriptors), and DbRuntime.mimic() fetches -> patches -> runs in the real Vm, proving 're-run or modify behavior without the original binary'. cubeai: models over cube-stored traces/graphs to classify blocks (Computation/Branch/CallTrampoline/IoSection/Sequence from the op-class histogram + behavior descriptors) and suggest new code sequences as cubedbt TranslationRules (persisted into the c220 rule band, discoverable by DbRuntime). End-to-end: trace -> classify -> suggest -> mimic. Both crates are dependency-free and operate on the real CubeStore/CodeCell/ Vm/Behavior types, so they are exercisable today on HashBackend and slot into ConcurrentStore later without an API change. ./check quick green: cubedbt 4 tests, cubeai 5 tests, full workspace green.
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
//! CUBELinux-2 AI layer.
|
||||
//!
|
||||
//! Per the PDF (Package 4, §551): *"cubeai: models that operate on
|
||||
//! cube‑stored traces/graphs to classify blocks, infer higher‑level
|
||||
//! operations, and suggest new code sequences or orchestrations."*
|
||||
//!
|
||||
//! This crate is the *structured decision* layer sitting on top of the
|
||||
//! substrate (and on [`cubedbt`]). It is deterministic and dependency-free
|
||||
//! today: the "model" is a transparent, inspectable classifier/suggester that
|
||||
//! operates on the same [`CubeStore`] / [`CodeCell`] / [`Behavior`] types the
|
||||
//! rest of the workspace uses. A learned model can later implement the same
|
||||
//! traits without changing callers.
|
||||
//!
|
||||
//! Pipeline (the end-to-end story from §549–§551 + §554):
|
||||
//! trace (cube) → classify blocks → infer higher-level op →
|
||||
//! suggest `TranslationRule`s → hand to `cubedbt` to patch + run (mimic).
|
||||
|
||||
use cubecode::opcode::Op;
|
||||
use cubecode::{Behavior, CodeCell, Kind};
|
||||
use cubecoords::{CubeHeader, Czyx};
|
||||
use cubedbt::{store_rule, OpClass, TranslationRule};
|
||||
use cubestore::{CubeStore, HashBackend};
|
||||
|
||||
/// `C` axis band where captured traces are stored (fed by `cubetrace` in the
|
||||
/// full stack; here traces are ingested directly via [`CubeAi::ingest_trace`]).
|
||||
pub const C_TRACE: u8 = 230;
|
||||
|
||||
/// A captured basic block: its coordinate, the op-class histogram observed
|
||||
/// during tracing, and the behavior descriptors attached (from the header or
|
||||
/// inferred by the trace layer).
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct BlockTrace {
|
||||
pub label: Czyx,
|
||||
/// Count of each [`OpClass`] seen in the block (`OpClass::Any` unused).
|
||||
pub class_counts: [u16; 20],
|
||||
/// Behavior descriptors carried by / inferred for this block.
|
||||
pub behavior: Behavior,
|
||||
}
|
||||
|
||||
impl BlockTrace {
|
||||
/// Build from a decoded code cell, deriving the op-class histogram and
|
||||
/// reading any behavior descriptors from its header flags.
|
||||
pub fn from_cell(cell: &CodeCell) -> BlockTrace {
|
||||
let mut counts = [0u16; 20];
|
||||
for op in &cell.code {
|
||||
let c = OpClass::of(op) as usize;
|
||||
if c < 20 {
|
||||
counts[c] += 1;
|
||||
}
|
||||
}
|
||||
BlockTrace {
|
||||
label: cell.label,
|
||||
class_counts: counts,
|
||||
behavior: Behavior::from_flags(cell.header.flags.bits()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Total op count.
|
||||
pub fn total(&self) -> u32 {
|
||||
self.class_counts.iter().map(|&c| c as u32).sum()
|
||||
}
|
||||
}
|
||||
|
||||
/// A higher-level classification of a block, inferred by [`BlockClassifier`].
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum BlockKind {
|
||||
/// Mostly arithmetic/logic — a computation block.
|
||||
Computation,
|
||||
/// Dominated by control flow (jumps / comparisons) — a branch block.
|
||||
Branch,
|
||||
/// Contains call links to other records — a call/composition block.
|
||||
CallTrampoline,
|
||||
/// Heavy I/O or network descriptors — an I/O section.
|
||||
IoSection,
|
||||
/// Otherwise: a plain linear sequence.
|
||||
Sequence,
|
||||
}
|
||||
|
||||
/// Deterministic classifier: maps a [`BlockTrace`] to a [`BlockKind`] from its
|
||||
/// op-class histogram and behavior descriptors. (The transparent "model".)
|
||||
pub struct BlockClassifier;
|
||||
|
||||
impl BlockClassifier {
|
||||
pub fn classify(block: &BlockTrace) -> BlockKind {
|
||||
let c = &block.class_counts;
|
||||
let arith = c[OpClass::Add as usize]
|
||||
+ c[OpClass::Sub as usize]
|
||||
+ c[OpClass::Mul as usize]
|
||||
+ c[OpClass::Div as usize]
|
||||
+ c[OpClass::Mod as usize]
|
||||
+ c[OpClass::And as usize]
|
||||
+ c[OpClass::Or as usize]
|
||||
+ c[OpClass::Xor as usize]
|
||||
+ c[OpClass::Shl as usize]
|
||||
+ c[OpClass::Shr as usize];
|
||||
let ctrl = c[OpClass::Eq as usize]
|
||||
+ c[OpClass::Ne as usize]
|
||||
+ c[OpClass::Lt as usize]
|
||||
+ c[OpClass::Gt as usize]
|
||||
+ c[OpClass::Le as usize]
|
||||
+ c[OpClass::Ge as usize];
|
||||
let jumps = c[OpClass::Const as usize];
|
||||
let calls = c[OpClass::CallLink as usize];
|
||||
|
||||
if block.behavior.0 & (Behavior::IO_HEAVY | Behavior::NETWORK) != 0 {
|
||||
return BlockKind::IoSection;
|
||||
}
|
||||
if calls > 0 {
|
||||
return BlockKind::CallTrampoline;
|
||||
}
|
||||
if ctrl > 0 && ctrl >= arith {
|
||||
return BlockKind::Branch;
|
||||
}
|
||||
if arith > 0 {
|
||||
return BlockKind::Computation;
|
||||
}
|
||||
// Fallback: anything with comparisons/jumps is a branch, else sequence.
|
||||
if jumps > 0 || ctrl > 0 {
|
||||
BlockKind::Branch
|
||||
} else {
|
||||
BlockKind::Sequence
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Suggest transformations for a block, returning candidate [`TranslationRule`]s
|
||||
/// the DBT layer can apply (PDF: "suggest new code sequences or
|
||||
/// orchestrations"). Deterministic and local: each suggestion records *why*.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Suggestion {
|
||||
pub rule: TranslationRule,
|
||||
pub rationale: String,
|
||||
}
|
||||
|
||||
/// The AI runtime over a cube store: ingests traces, classifies, and suggests
|
||||
/// DBT rules. Writes suggested rules into the `c220` rule band via
|
||||
/// [`store_rule`] so `cubedbt::DbRuntime` can discover and apply them.
|
||||
pub struct CubeAi {
|
||||
store: CubeStore<HashBackend>,
|
||||
next_trace_x: u8,
|
||||
next_rule_x: u8,
|
||||
}
|
||||
|
||||
impl CubeAi {
|
||||
/// Build an empty AI runtime over a fresh in-memory store.
|
||||
pub fn new() -> Self {
|
||||
CubeAi {
|
||||
store: CubeStore::new(HashBackend::new()),
|
||||
next_trace_x: 1,
|
||||
next_rule_x: 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Ingest a captured trace (e.g. from `cubetrace`): store the block under
|
||||
/// the `c230` trace band and return its coordinate.
|
||||
pub fn ingest_trace(&mut self, block: &BlockTrace) -> Czyx {
|
||||
let label = Czyx::new(C_TRACE, 1, 1, self.next_trace_x);
|
||||
self.next_trace_x = self.next_trace_x.wrapping_add(1).max(1);
|
||||
let mut h = CubeHeader::new();
|
||||
h.title = Some(format!("trace:{:?}", block.label));
|
||||
h.doc_type = Some(Kind::Other.as_str().into());
|
||||
h.size_bytes = Some(block.class_counts.len() as u64 * 2);
|
||||
h.flags.0 |= block.behavior.to_flags();
|
||||
h.refresh_flags();
|
||||
// Body: the raw histogram (20 x u16 le).
|
||||
let mut body = Vec::with_capacity(40);
|
||||
for c in &block.class_counts {
|
||||
body.extend_from_slice(&c.to_le_bytes());
|
||||
}
|
||||
self.store.put_record(label, &h, &body);
|
||||
label
|
||||
}
|
||||
|
||||
/// Classify a single block.
|
||||
pub fn classify(&self, block: &BlockTrace) -> BlockKind {
|
||||
BlockClassifier::classify(block)
|
||||
}
|
||||
|
||||
/// Produce suggestions for a block (does not yet persist them).
|
||||
pub fn suggest(&self, block: &BlockTrace) -> Vec<Suggestion> {
|
||||
let kind = BlockClassifier::classify(block);
|
||||
let mut out = Vec::new();
|
||||
match kind {
|
||||
BlockKind::Computation => {
|
||||
// Suggest vectorizing a repeated multiply-by-constant:
|
||||
// Mul(const) -> Const(shift?), but keep it conservative:
|
||||
// replace Mul with a left-shift when operand is a power of two.
|
||||
out.push(Suggestion {
|
||||
rule: TranslationRule {
|
||||
name: format!("compute-opt:{:?}", block.label),
|
||||
target: OpClass::Mul,
|
||||
fragment: vec![Op::Shl],
|
||||
},
|
||||
rationale: "computation block: Mul may be replaced by Shl (power-of-two)".into(),
|
||||
});
|
||||
}
|
||||
BlockKind::Branch => {
|
||||
out.push(Suggestion {
|
||||
rule: TranslationRule {
|
||||
name: format!("branch-opt:{:?}", block.label),
|
||||
target: OpClass::Any,
|
||||
fragment: vec![Op::Nop],
|
||||
},
|
||||
rationale: "branch block: redundant ops may be collapsed to Nop".into(),
|
||||
});
|
||||
}
|
||||
BlockKind::IoSection => {
|
||||
out.push(Suggestion {
|
||||
rule: TranslationRule {
|
||||
name: format!("io-batch:{:?}", block.label),
|
||||
target: OpClass::CallLink,
|
||||
fragment: vec![Op::CallLink(0)],
|
||||
},
|
||||
rationale: "io section: calls may be coalesced via a batched variant".into(),
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Ingest a block and persist its suggestions as DBT rules in the `c220`
|
||||
/// band. Returns the stored rule coordinates (empty if no suggestions).
|
||||
pub fn ingest_and_suggest(&mut self, block: &BlockTrace) -> Vec<Czyx> {
|
||||
self.ingest_trace(block);
|
||||
let sugs = self.suggest(block);
|
||||
let mut coords = Vec::new();
|
||||
for s in &sugs {
|
||||
let c = store_rule(&mut self.store, &s.rule, self.next_rule_x);
|
||||
self.next_rule_x = self.next_rule_x.wrapping_add(1).max(1);
|
||||
coords.push(c);
|
||||
}
|
||||
coords
|
||||
}
|
||||
|
||||
/// Borrow the backing store (e.g. to hand to `cubedbt::DbRuntime`).
|
||||
pub fn store(&self) -> &CubeStore<HashBackend> {
|
||||
&self.store
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use cubecode::opcode::Op;
|
||||
use cubecoords::Czyx;
|
||||
|
||||
fn cell(coord: Czyx, code: Vec<Op>, beh: Behavior) -> CodeCell {
|
||||
let mut h = CubeHeader::new();
|
||||
h.title = Some("blk".into());
|
||||
h.doc_type = Some(Kind::Fn.as_str().into());
|
||||
h.flags.0 |= beh.to_flags();
|
||||
h.refresh_flags();
|
||||
CodeCell::from_record(coord, &h, &cubecode::opcode::encode(&code))
|
||||
.expect("cell is valid bytecode")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trace_classifies_computation() {
|
||||
let c = cell(
|
||||
Czyx::new(1, 1, 1, 1),
|
||||
vec![Op::Const(3), Op::Const(4), Op::Mul, Op::Halt],
|
||||
Behavior(Behavior::PURE),
|
||||
);
|
||||
let t = BlockTrace::from_cell(&c);
|
||||
assert_eq!(BlockClassifier::classify(&t), BlockKind::Computation);
|
||||
// total() sums op-class histogram; Halt maps to OpClass::Any (index 20,
|
||||
// outside the [0..20) histogram), so 3 counted ops (Const, Const, Mul).
|
||||
assert_eq!(t.total(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trace_classifies_io_section() {
|
||||
let c = cell(
|
||||
Czyx::new(1, 1, 1, 2),
|
||||
vec![Op::Const(1), Op::Halt],
|
||||
Behavior(Behavior::IO_HEAVY | Behavior::NETWORK),
|
||||
);
|
||||
let t = BlockTrace::from_cell(&c);
|
||||
assert_eq!(BlockClassifier::classify(&t), BlockKind::IoSection);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trace_classifies_call_trampoline() {
|
||||
let c = cell(
|
||||
Czyx::new(1, 1, 1, 3),
|
||||
vec![Op::CallLink(0), Op::CallLink(1), Op::Halt],
|
||||
Behavior::default(),
|
||||
);
|
||||
let t = BlockTrace::from_cell(&c);
|
||||
assert_eq!(BlockClassifier::classify(&t), BlockKind::CallTrampoline);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suggest_emits_rule_and_persists() {
|
||||
let c = cell(
|
||||
Czyx::new(1, 1, 1, 4),
|
||||
vec![Op::Const(3), Op::Const(4), Op::Mul, Op::Halt],
|
||||
Behavior(Behavior::PURE),
|
||||
);
|
||||
let t = BlockTrace::from_cell(&c);
|
||||
let mut ai = CubeAi::new();
|
||||
let coords = ai.ingest_and_suggest(&t);
|
||||
assert_eq!(coords.len(), 1, "computation block suggests one rule");
|
||||
assert_eq!(coords[0].c, rule_band());
|
||||
// The stored rule is discoverable by cubedbt.
|
||||
let rt = cubedbt::DbRuntime::new(ai.store().clone());
|
||||
assert_eq!(rt.discover_rules().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ingest_trace_stores_under_c230() {
|
||||
let c = cell(Czyx::new(1, 1, 1, 5), vec![Op::Halt], Behavior::default());
|
||||
let t = BlockTrace::from_cell(&c);
|
||||
let mut ai = CubeAi::new();
|
||||
let coord = ai.ingest_trace(&t);
|
||||
assert_eq!(coord.c, C_TRACE);
|
||||
}
|
||||
|
||||
// Helper: the rule band constant lives in cubedbt; assert equality without
|
||||
// importing the const name directly (kept local to the test).
|
||||
fn rule_band() -> u8 {
|
||||
cubedbt::C_DBT_RULE
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user