use crate::artefact::{ArtefactManifest, ModelPaths, hash_bytes};
use cyberbrain_core::{Error, Result};
use safetensors::Dtype;
use safetensors::tensor::TensorView;
use std::path::Path;
pub const UNK: &str = "[UNK]";
pub fn write_synthetic_model(
dir: &Path,
vocab: &[&str],
dim: usize,
seed: u64,
) -> Result<(ModelPaths, ArtefactManifest)> {
let paths = ModelPaths::in_dir(dir);
let tokenizer_json = tokenizer_json(vocab)?;
let weights = weights_bytes(vocab.len() + 1, dim, seed)?;
write(&paths.tokenizer, tokenizer_json.as_bytes())?;
write(&paths.weights, &weights)?;
let manifest = ArtefactManifest {
weights_blake3: hash_bytes(&weights),
tokenizer_blake3: hash_bytes(tokenizer_json.as_bytes()),
};
Ok((paths, manifest))
}
pub fn synthetic_weights(rows: usize, dim: usize, seed: u64) -> Vec<f32> {
let mut state = seed ^ 0x9E37_79B9_7F4A_7C15;
(0..rows * dim)
.map(|_| {
state ^= state >> 12;
state ^= state << 25;
state ^= state >> 27;
let r = state.wrapping_mul(0x2545_F491_4F6C_DD1D);
((r >> 11) as f64 / (1u64 << 53) as f64 * 2.0 - 1.0) as f32
})
.collect()
}
fn weights_bytes(rows: usize, dim: usize, seed: u64) -> Result<Vec<u8>> {
let data = synthetic_weights(rows, dim, seed);
let raw: Vec<u8> = data.iter().flat_map(|f| f.to_le_bytes()).collect();
let view = TensorView::new(Dtype::F32, vec![rows, dim], &raw)
.map_err(|e| Error::Embed(format!("synthetic tensor: {e}")))?;
safetensors::serialize([(crate::EMBEDDINGS_TENSOR, view)], None)
.map_err(|e| Error::Embed(format!("synthetic safetensors: {e}")))
}
fn tokenizer_json(vocab: &[&str]) -> Result<String> {
let mut entries = vec![format!("{UNK:?}:0")];
for (i, word) in vocab.iter().enumerate() {
if *word == UNK || word.chars().any(char::is_whitespace) || word.contains('"') {
return Err(Error::Embed(format!(
"synthetic vocab entry {word:?} is not a plain word"
)));
}
entries.push(format!("{word:?}:{}", i + 1));
}
Ok(format!(
concat!(
"{{\"version\":\"1.0\",\"truncation\":null,\"padding\":null,",
"\"added_tokens\":[],",
"\"normalizer\":{{\"type\":\"Lowercase\"}},",
"\"pre_tokenizer\":{{\"type\":\"Whitespace\"}},",
"\"post_processor\":null,\"decoder\":null,",
"\"model\":{{\"type\":\"WordLevel\",\"vocab\":{{{}}},\"unk_token\":{:?}}}}}"
),
entries.join(","),
UNK
))
}
fn write(path: &Path, bytes: &[u8]) -> Result<()> {
std::fs::write(path, bytes).map_err(|source| Error::Io {
path: path.to_path_buf(),
source,
})
}