use kopitiam_tokenizer::byte_map::byte_to_unicode;
const ALIGNMENT: usize = 32;
const GGML_TYPE_F32: u32 = 0;
const GGUF_VALUE_TYPE_I32: u32 = 5;
const GGUF_VALUE_TYPE_U32: u32 = 4;
const GGUF_VALUE_TYPE_F32: u32 = 6;
const GGUF_VALUE_TYPE_STRING: u32 = 8;
const GGUF_VALUE_TYPE_ARRAY: u32 = 9;
const TOKEN_TYPE_NORMAL: i32 = 1;
const TOKEN_TYPE_CONTROL: i32 = 3;
const CONTROL_TOKENS: [&str; 3] = ["<|endoftext|>", "<|im_start|>", "<|im_end|>"];
fn push_u32(buf: &mut Vec<u8>, v: u32) {
buf.extend_from_slice(&v.to_le_bytes());
}
fn push_u64(buf: &mut Vec<u8>, v: u64) {
buf.extend_from_slice(&v.to_le_bytes());
}
fn push_i32(buf: &mut Vec<u8>, v: i32) {
buf.extend_from_slice(&v.to_le_bytes());
}
fn push_string(buf: &mut Vec<u8>, s: &str) {
push_u64(buf, s.len() as u64);
buf.extend_from_slice(s.as_bytes());
}
fn pad_to_alignment(buf: &mut Vec<u8>, alignment: usize) {
let pad = alignment.wrapping_sub(buf.len() % alignment) % alignment;
buf.extend(std::iter::repeat_n(0u8, pad));
}
struct Xorshift64(u64);
impl Xorshift64 {
fn new(seed: u64) -> Self {
Self(seed)
}
fn next_weight(&mut self) -> f32 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
let unit = (self.0 >> 40) as u32 as f32 / (1u32 << 24) as f32; (unit - 0.5) * 0.2
}
}
struct GgufBuilder {
kv_count: u64,
kvs: Vec<u8>,
tensor_count: u64,
tensor_infos: Vec<u8>,
tensor_data: Vec<u8>,
}
impl GgufBuilder {
fn new() -> Self {
Self { kv_count: 0, kvs: Vec::new(), tensor_count: 0, tensor_infos: Vec::new(), tensor_data: Vec::new() }
}
fn kv_string(&mut self, key: &str, value: &str) {
push_string(&mut self.kvs, key);
push_u32(&mut self.kvs, GGUF_VALUE_TYPE_STRING);
push_string(&mut self.kvs, value);
self.kv_count += 1;
}
fn kv_u32(&mut self, key: &str, value: u32) {
push_string(&mut self.kvs, key);
push_u32(&mut self.kvs, GGUF_VALUE_TYPE_U32);
push_u32(&mut self.kvs, value);
self.kv_count += 1;
}
fn kv_f32(&mut self, key: &str, value: f32) {
push_string(&mut self.kvs, key);
push_u32(&mut self.kvs, GGUF_VALUE_TYPE_F32);
self.kvs.extend_from_slice(&value.to_le_bytes());
self.kv_count += 1;
}
fn kv_string_array(&mut self, key: &str, values: &[String]) {
push_string(&mut self.kvs, key);
push_u32(&mut self.kvs, GGUF_VALUE_TYPE_ARRAY);
push_u32(&mut self.kvs, GGUF_VALUE_TYPE_STRING);
push_u64(&mut self.kvs, values.len() as u64);
for v in values {
push_string(&mut self.kvs, v);
}
self.kv_count += 1;
}
fn kv_i32_array(&mut self, key: &str, values: &[i32]) {
push_string(&mut self.kvs, key);
push_u32(&mut self.kvs, GGUF_VALUE_TYPE_ARRAY);
push_u32(&mut self.kvs, GGUF_VALUE_TYPE_I32);
push_u64(&mut self.kvs, values.len() as u64);
for &v in values {
push_i32(&mut self.kvs, v);
}
self.kv_count += 1;
}
fn tensor_f32(&mut self, name: &str, shape: &[usize], data: &[f32]) {
assert_eq!(shape.iter().product::<usize>(), data.len(), "tensor {name}: shape/data length mismatch");
pad_to_alignment(&mut self.tensor_data, ALIGNMENT);
let relative_offset = self.tensor_data.len() as u64;
let ne: Vec<u64> = shape.iter().rev().map(|&d| d as u64).collect();
push_string(&mut self.tensor_infos, name);
push_u32(&mut self.tensor_infos, ne.len() as u32);
for &d in &ne {
push_u64(&mut self.tensor_infos, d);
}
push_u32(&mut self.tensor_infos, GGML_TYPE_F32);
push_u64(&mut self.tensor_infos, relative_offset);
self.tensor_count += 1;
for &v in data {
self.tensor_data.extend_from_slice(&v.to_le_bytes());
}
}
fn build(self) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(b"GGUF");
push_u32(&mut buf, 3); push_u64(&mut buf, self.tensor_count);
push_u64(&mut buf, self.kv_count);
buf.extend_from_slice(&self.kvs);
buf.extend_from_slice(&self.tensor_infos);
pad_to_alignment(&mut buf, ALIGNMENT);
buf.extend_from_slice(&self.tensor_data);
buf
}
}
fn vocab_size() -> usize {
256 + CONTROL_TOKENS.len()
}
pub(crate) fn build_local_adapter_fixture() -> Vec<u8> {
let n_layers = 2usize;
let n_heads = 4usize;
let n_kv_heads = 2usize;
let hidden = 16usize;
let head_dim = hidden / n_heads;
let ffn = 32usize;
let vocab = vocab_size();
let context_length = 64u32;
let mut rng = Xorshift64::new(0xC0FFEE_u64);
let fill = |n: usize, rng: &mut Xorshift64| -> Vec<f32> { (0..n).map(|_| rng.next_weight()).collect() };
let mut g = GgufBuilder::new();
let arch = "qwen2";
g.kv_string("general.architecture", arch);
g.kv_string("general.name", "kopitiam-test-qwen");
g.kv_u32(&format!("{arch}.block_count"), n_layers as u32);
g.kv_u32(&format!("{arch}.attention.head_count"), n_heads as u32);
g.kv_u32(&format!("{arch}.attention.head_count_kv"), n_kv_heads as u32);
g.kv_u32(&format!("{arch}.embedding_length"), hidden as u32);
g.kv_u32(&format!("{arch}.feed_forward_length"), ffn as u32);
g.kv_u32(&format!("{arch}.context_length"), context_length);
g.kv_f32(&format!("{arch}.rope.freq_base"), 10_000.0);
g.kv_f32(&format!("{arch}.attention.layer_norm_rms_epsilon"), 1e-6);
let mut tokens: Vec<String> = (0u16..=255).map(|b| byte_to_unicode(b as u8).to_string()).collect();
for &special in &CONTROL_TOKENS {
tokens.push(special.to_string());
}
g.kv_string_array("tokenizer.ggml.tokens", &tokens);
g.kv_string_array("tokenizer.ggml.merges", &[]); let mut token_types = vec![TOKEN_TYPE_NORMAL; 256];
token_types.extend(std::iter::repeat_n(TOKEN_TYPE_CONTROL, CONTROL_TOKENS.len()));
g.kv_i32_array("tokenizer.ggml.token_type", &token_types);
g.tensor_f32("token_embd.weight", &[vocab, hidden], &fill(vocab * hidden, &mut rng));
let kv_dim = n_kv_heads * head_dim;
for layer in 0..n_layers {
let p = |suffix: &str| format!("blk.{layer}.{suffix}");
g.tensor_f32(&p("attn_norm.weight"), &[hidden], &fill(hidden, &mut rng));
g.tensor_f32(&p("attn_q.weight"), &[hidden, hidden], &fill(hidden * hidden, &mut rng));
g.tensor_f32(&p("attn_q.bias"), &[hidden], &fill(hidden, &mut rng));
g.tensor_f32(&p("attn_k.weight"), &[kv_dim, hidden], &fill(kv_dim * hidden, &mut rng));
g.tensor_f32(&p("attn_k.bias"), &[kv_dim], &fill(kv_dim, &mut rng));
g.tensor_f32(&p("attn_v.weight"), &[kv_dim, hidden], &fill(kv_dim * hidden, &mut rng));
g.tensor_f32(&p("attn_v.bias"), &[kv_dim], &fill(kv_dim, &mut rng));
g.tensor_f32(&p("attn_output.weight"), &[hidden, hidden], &fill(hidden * hidden, &mut rng));
g.tensor_f32(&p("ffn_norm.weight"), &[hidden], &fill(hidden, &mut rng));
g.tensor_f32(&p("ffn_gate.weight"), &[ffn, hidden], &fill(ffn * hidden, &mut rng));
g.tensor_f32(&p("ffn_up.weight"), &[ffn, hidden], &fill(ffn * hidden, &mut rng));
g.tensor_f32(&p("ffn_down.weight"), &[hidden, ffn], &fill(hidden * ffn, &mut rng));
}
g.tensor_f32("output_norm.weight", &[hidden], &fill(hidden, &mut rng));
g.build()
}
pub(crate) fn write_temp_gguf(bytes: &[u8], disambiguator: &str) -> std::path::PathBuf {
use std::io::Write;
let mut tmp = tempfile::Builder::new()
.prefix(&format!("kopitiam-ai-test-{disambiguator}-"))
.suffix(".gguf")
.tempfile()
.expect("create a uniquely-named temp file");
tmp.write_all(bytes).expect("write synthetic GGUF fixture");
tmp.flush().expect("flush synthetic GGUF fixture");
let (_file, path) = tmp.keep().expect("persist temp GGUF fixture past this function's return");
path
}
#[cfg(test)]
mod tests {
use super::*;
use kopitiam_tokenizer::Tokenizer as _;
#[test]
fn fixture_parses_as_valid_gguf_with_expected_shapes_and_vocab() {
let bytes = build_local_adapter_fixture();
let path = write_temp_gguf(&bytes, "self-check");
let model = kopitiam_loader::load_model(&path).unwrap();
assert_eq!(model.format(), "gguf");
let embd = model.tensor("token_embd.weight").unwrap();
assert_eq!(embd.shape.dims(), &[vocab_size(), 16]);
assert!(model.tensor("blk.0.attn_q.weight").is_some());
assert!(model.tensor("blk.1.ffn_down.weight").is_some());
assert!(model.tensor("output.weight").is_none(), "fixture ties embeddings");
assert_eq!(model.metadata().vocab_size, Some(vocab_size() as u64));
assert_eq!(model.metadata().name.as_deref(), Some("kopitiam-test-qwen"));
}
#[test]
fn fixture_tokenizer_round_trips_and_exposes_chatml_control_tokens() {
let bytes = build_local_adapter_fixture();
let path = write_temp_gguf(&bytes, "tokenizer-self-check");
let model = kopitiam_loader::load_model(&path).unwrap();
let tokenizer = kopitiam_runtime::tokenizer_from_gguf(&model).unwrap();
assert_eq!(tokenizer.special_token_id("<|im_start|>"), Some(257));
assert_eq!(tokenizer.special_token_id("<|im_end|>"), Some(258));
assert_eq!(tokenizer.special_token_id("<|endoftext|>"), Some(256));
let text = "hello world";
let ids = tokenizer.encode(text).unwrap();
assert_eq!(tokenizer.decode(&ids).unwrap(), text);
}
}