use std::io::Write;
use tru::graph::Cyberlink;
use tru::graph::record::RECORD_SIZE;
fn cid_id(c: &str) -> [u8; 32] {
let mut h = [0u8; 32];
h.copy_from_slice(cyber_hemera::hash(c.as_bytes()).as_bytes());
h
}
fn main() {
let argv: Vec<String> = std::env::args().skip(1).collect();
if argv.len() < 2 {
die("usage: compile_graph <links.jsonl> <out.model> [--name NAME] [--index PATH]");
}
let input = &argv[0];
let output = &argv[1];
let mut name = "compiled-graph".to_string();
let mut index_path: Option<String> = None;
let mut i = 2;
while i < argv.len() {
match argv[i].as_str() {
"--name" => {
i += 1;
name = argv.get(i).cloned().unwrap_or(name);
}
"--index" => {
i += 1;
index_path = argv.get(i).cloned();
}
other => die(&format!("unknown flag {other}")),
}
i += 1;
}
let t0 = std::time::Instant::now();
let mut links: Vec<Cyberlink> = Vec::new();
let mut order: Vec<Option<String>> = Vec::new();
let mut seen: std::collections::HashMap<[u8; 32], usize> = std::collections::HashMap::new();
let text =
std::fs::read_to_string(input).unwrap_or_else(|e| die(&format!("read {input}: {e}")));
for line in text.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
let h: u64 = line
.split_once("\"h\": ")
.and_then(|(_, r)| r.split(',').next())
.and_then(|s| s.trim().parse().ok())
.unwrap_or_else(|| die("bad height field"));
let cid = |key: &str| -> String {
line.split_once(&format!("\"{key}\": \""))
.and_then(|(_, r)| r.split_once('"'))
.unwrap_or_else(|| die("bad cid field"))
.0
.to_string()
};
let fc = cid("f");
let tc = cid("t");
let fh = cid_id(&fc);
let th = cid_id(&tc);
links.push(Cyberlink {
neuron: cid_id("neuron-compile"),
from: fh,
to: th,
token: 1,
amount: 1,
valence: 1,
block: h,
});
for (h, cid) in [
(fh, Some(fc.clone())),
(th, Some(tc)),
(tru::pass::index::axon(&fh, &th), None),
] {
if let std::collections::hash_map::Entry::Vacant(e) = seen.entry(h) {
e.insert(order.len());
order.push(cid);
}
}
}
println!("loaded {} links ({:?})", links.len(), t0.elapsed());
let mut records = Vec::with_capacity(links.len() * RECORD_SIZE);
for l in &links {
let mut r = [0u8; RECORD_SIZE];
r[0..32].copy_from_slice(&l.neuron);
r[32..64].copy_from_slice(&l.from);
r[64..96].copy_from_slice(&l.to);
r[96..100].copy_from_slice(&l.token.to_le_bytes());
r[100..116].copy_from_slice(&l.amount.to_le_bytes());
r[116] = l.valence as u8;
r[117..125].copy_from_slice(&l.block.to_le_bytes());
records.extend_from_slice(&r);
}
let graph_path = std::env::temp_dir().join(format!("tru_compile_{}.graph", std::process::id()));
let frontmatter = format!(
"[cyb]\ntypes = [\"graph\"]\nname = \"{name}\"\n\nfiles\nname = \"config\"\nformat = \"toml\"\n\nfiles\nname = \"cyberlinks\"\nformat = \"records\"\nsize = {}\n",
records.len()
);
{
let mut f = std::fs::File::create(&graph_path).unwrap();
f.write_all(frontmatter.as_bytes()).unwrap();
f.write_all(b"~~~config\nchain_id = \"compile\"\n").unwrap();
f.write_all(b"~~~cyberlinks\n").unwrap();
f.write_all(&records).unwrap();
}
println!("graph file {} ({:?})", graph_path.display(), t0.elapsed());
let g =
tru::graph::Graph::open(&graph_path).unwrap_or_else(|e| die(&format!("open graph: {e}")));
println!("compiling {} ...", g.name());
let tc = std::time::Instant::now();
let mut model =
tru::pass::compile::compile(&g).unwrap_or_else(|e| die(&format!("compile: {e}")));
{
let mut v = String::from("[tokens]\n");
for (id, cid) in order.iter().enumerate() {
match cid {
Some(c) => v.push_str(&format!("{id} = \"{c}\"\n")),
None => {
let hex = &model.vocab;
let _ = hex;
v.push_str(&format!("{id} = \"0x{id:064x}\"\n"));
}
}
}
model.vocab = v;
}
println!("compiled in {:?}", tc.elapsed());
{
let t = &model.tensors[0];
let d = t.shape[1] as usize;
for i in [0usize, 1, 100] {
let mut n2 = 0.0f64;
for c in 0..d {
let v = t.data[i * d + c].to_f64();
n2 += v * v;
}
println!("row-norm check [{i}]: ||E||^2 = {n2:.4}");
}
}
model
.write(output)
.unwrap_or_else(|e| die(&format!("write model: {e}")));
let bytes = std::fs::metadata(output).map(|m| m.len()).unwrap_or(0);
let cfg = &model.config;
let grab = |key: &str| -> String {
cfg.lines()
.find_map(|l| {
l.split_once('=')
.filter(|(k, _)| k.trim() == key)
.map(|(_, v)| v.trim().to_string())
})
.unwrap_or_default()
};
println!(
"model {output}: {bytes} bytes ยท d*={} h*={} L*={} ยท wall {:?}",
grab("hidden_size"),
grab("num_attention_heads"),
grab("num_hidden_layers"),
t0.elapsed()
);
if let Some(ip) = &index_path {
let t = &model.tensors[0];
let d = t.shape[1] as usize;
let n = t.shape[0] as usize;
let mut out = Vec::with_capacity(16 + n * 64 + n * d * 4);
out.extend_from_slice(&(n as u64).to_le_bytes());
out.extend_from_slice(&(d as u64).to_le_bytes());
for cid in &order {
let s = cid.as_deref().unwrap_or("");
let b = s.as_bytes();
let len = b.len().min(59) as u8;
out.push(len);
let mut slot = [0u8; 59];
slot[..len as usize].copy_from_slice(&b[..len as usize]);
out.extend_from_slice(&slot);
}
for v in &t.data {
out.extend_from_slice(&(v.to_f64() as f32).to_le_bytes());
}
std::fs::write(ip, &out).unwrap_or_else(|e| die(&format!("index: {e}")));
println!("index {ip}: {} tokens ({} bytes)", n, out.len());
}
let _ = std::fs::remove_file(&graph_path);
}
fn die(msg: &str) -> ! {
eprintln!("compile_graph: {msg}");
std::process::exit(2)
}