use super::types::{
GGUF_ALIGNMENT, GGUF_MAGIC, GGUF_TYPE_BF16, GGUF_TYPE_F16, GGUF_TYPE_F32, GGUF_TYPE_Q2_K,
GGUF_TYPE_Q3_K, GGUF_TYPE_Q4_0, GGUF_TYPE_Q4_1, GGUF_TYPE_Q4_K, GGUF_TYPE_Q5_0, GGUF_TYPE_Q5_1,
GGUF_TYPE_Q5_K, GGUF_TYPE_Q6_K, GGUF_TYPE_Q8_0, GGUF_VERSION_V3,
};
pub struct GGUFBuilder {
metadata: Vec<(String, u32, Vec<u8>)>,
tensors: Vec<(String, Vec<u64>, u32, Vec<u8>)>,
}
impl Default for GGUFBuilder {
fn default() -> Self {
Self::new()
}
}
impl GGUFBuilder {
#[must_use]
pub fn new() -> Self {
Self {
metadata: Vec::new(),
tensors: Vec::new(),
}
}
#[must_use]
pub fn add_string(mut self, key: &str, value: &str) -> Self {
let mut bytes = Vec::new();
bytes.extend_from_slice(&(value.len() as u64).to_le_bytes());
bytes.extend_from_slice(value.as_bytes());
self.metadata.push((key.to_string(), 8, bytes)); self
}
#[must_use]
pub fn add_u32(mut self, key: &str, value: u32) -> Self {
self.metadata
.push((key.to_string(), 4, value.to_le_bytes().to_vec())); self
}
#[must_use]
pub fn add_f32(mut self, key: &str, value: f32) -> Self {
self.metadata
.push((key.to_string(), 6, value.to_le_bytes().to_vec())); self
}
#[must_use]
pub fn architecture(self, arch: &str) -> Self {
self.add_string(super::keys::GENERAL_ARCHITECTURE, arch)
}
#[must_use]
pub fn hidden_dim(self, arch: &str, dim: u32) -> Self {
self.add_u32(
&super::keys::arch_key(arch, super::keys::EMBEDDING_LENGTH),
dim,
)
}
#[must_use]
pub fn num_layers(self, arch: &str, count: u32) -> Self {
self.add_u32(
&super::keys::arch_key(arch, super::keys::BLOCK_COUNT),
count,
)
}
#[must_use]
pub fn num_heads(self, arch: &str, count: u32) -> Self {
self.add_u32(
&super::keys::arch_key(arch, super::keys::ATTENTION_HEAD_COUNT),
count,
)
}
#[must_use]
pub fn num_kv_heads(self, arch: &str, count: u32) -> Self {
self.add_u32(
&super::keys::arch_key(arch, super::keys::ATTENTION_HEAD_COUNT_KV),
count,
)
}
#[must_use]
pub fn context_length(self, arch: &str, len: u32) -> Self {
self.add_u32(
&super::keys::arch_key(arch, super::keys::CONTEXT_LENGTH),
len,
)
}
#[must_use]
pub fn rope_freq_base(self, arch: &str, base: f32) -> Self {
self.add_f32(
&super::keys::arch_key(arch, super::keys::ROPE_FREQ_BASE),
base,
)
}
#[must_use]
pub fn rms_epsilon(self, arch: &str, eps: f32) -> Self {
self.add_f32(
&super::keys::arch_key(arch, super::keys::ATTENTION_LAYER_NORM_RMS_EPSILON),
eps,
)
}
#[must_use]
pub fn ffn_hidden_dim(self, arch: &str, dim: u32) -> Self {
self.add_u32(
&super::keys::arch_key(arch, super::keys::FEED_FORWARD_LENGTH),
dim,
)
}
#[must_use]
pub fn vocab_size(self, _arch: &str, size: u32) -> Self {
self.add_u32(super::keys::TOKENIZER_TOKENS_SIZE, size)
}
#[must_use]
pub fn add_f32_tensor(mut self, name: &str, dims: &[u64], data: &[f32]) -> Self {
let bytes: Vec<u8> = data.iter().flat_map(|f| f.to_le_bytes()).collect();
self.tensors
.push((name.to_string(), dims.to_vec(), GGUF_TYPE_F32, bytes));
self
}
#[must_use]
pub fn add_q4_0_tensor(mut self, name: &str, dims: &[u64], data: &[u8]) -> Self {
self.tensors.push((
name.to_string(),
dims.to_vec(),
GGUF_TYPE_Q4_0,
data.to_vec(),
));
self
}
#[must_use]
pub fn add_q8_0_tensor(mut self, name: &str, dims: &[u64], data: &[u8]) -> Self {
self.tensors.push((
name.to_string(),
dims.to_vec(),
GGUF_TYPE_Q8_0,
data.to_vec(),
));
self
}
#[must_use]
pub fn add_q4_k_tensor(mut self, name: &str, dims: &[u64], data: &[u8]) -> Self {
self.tensors.push((
name.to_string(),
dims.to_vec(),
GGUF_TYPE_Q4_K,
data.to_vec(),
));
self
}
#[must_use]
pub fn add_q5_k_tensor(mut self, name: &str, dims: &[u64], data: &[u8]) -> Self {
self.tensors.push((
name.to_string(),
dims.to_vec(),
GGUF_TYPE_Q5_K,
data.to_vec(),
));
self
}
#[must_use]
pub fn add_q6_k_tensor(mut self, name: &str, dims: &[u64], data: &[u8]) -> Self {
self.tensors.push((
name.to_string(),
dims.to_vec(),
GGUF_TYPE_Q6_K,
data.to_vec(),
));
self
}
#[must_use]
pub fn add_q2_k_tensor(mut self, name: &str, dims: &[u64], data: &[u8]) -> Self {
self.tensors.push((
name.to_string(),
dims.to_vec(),
GGUF_TYPE_Q2_K,
data.to_vec(),
));
self
}
#[must_use]
pub fn add_q3_k_tensor(mut self, name: &str, dims: &[u64], data: &[u8]) -> Self {
self.tensors.push((
name.to_string(),
dims.to_vec(),
GGUF_TYPE_Q3_K,
data.to_vec(),
));
self
}
#[must_use]
pub fn add_f16_tensor(mut self, name: &str, dims: &[u64], data: &[u8]) -> Self {
self.tensors.push((
name.to_string(),
dims.to_vec(),
GGUF_TYPE_F16,
data.to_vec(),
));
self
}
#[must_use]
pub fn add_bf16_tensor(mut self, name: &str, dims: &[u64], data: &[u8]) -> Self {
self.tensors.push((
name.to_string(),
dims.to_vec(),
GGUF_TYPE_BF16,
data.to_vec(),
));
self
}
#[must_use]
pub fn add_q4_1_tensor(mut self, name: &str, dims: &[u64], data: &[u8]) -> Self {
self.tensors.push((
name.to_string(),
dims.to_vec(),
GGUF_TYPE_Q4_1,
data.to_vec(),
));
self
}
#[must_use]
pub fn add_q5_0_tensor(mut self, name: &str, dims: &[u64], data: &[u8]) -> Self {
self.tensors.push((
name.to_string(),
dims.to_vec(),
GGUF_TYPE_Q5_0,
data.to_vec(),
));
self
}
#[must_use]
pub fn add_q5_1_tensor(mut self, name: &str, dims: &[u64], data: &[u8]) -> Self {
self.tensors.push((
name.to_string(),
dims.to_vec(),
GGUF_TYPE_Q5_1,
data.to_vec(),
));
self
}
#[must_use]
pub fn add_u8(mut self, key: &str, value: u8) -> Self {
self.metadata.push((key.to_string(), 0, vec![value]));
self
}
#[must_use]
pub fn add_i8(mut self, key: &str, value: i8) -> Self {
self.metadata.push((key.to_string(), 1, vec![value as u8]));
self
}
#[must_use]
pub fn add_u16(mut self, key: &str, value: u16) -> Self {
self.metadata
.push((key.to_string(), 2, value.to_le_bytes().to_vec()));
self
}
#[must_use]
pub fn add_i16(mut self, key: &str, value: i16) -> Self {
self.metadata
.push((key.to_string(), 3, value.to_le_bytes().to_vec()));
self
}
#[must_use]
pub fn add_i32(mut self, key: &str, value: i32) -> Self {
self.metadata
.push((key.to_string(), 5, value.to_le_bytes().to_vec()));
self
}
#[must_use]
pub fn add_bool(mut self, key: &str, value: bool) -> Self {
self.metadata
.push((key.to_string(), 7, vec![u8::from(value)]));
self
}
#[must_use]
pub fn add_u64(mut self, key: &str, value: u64) -> Self {
self.metadata
.push((key.to_string(), 10, value.to_le_bytes().to_vec()));
self
}
#[must_use]
pub fn add_i64(mut self, key: &str, value: i64) -> Self {
self.metadata
.push((key.to_string(), 11, value.to_le_bytes().to_vec()));
self
}
#[must_use]
pub fn add_f64(mut self, key: &str, value: f64) -> Self {
self.metadata
.push((key.to_string(), 12, value.to_le_bytes().to_vec()));
self
}
#[must_use]
pub fn add_string_array(mut self, key: &str, values: &[&str]) -> Self {
let mut bytes = Vec::new();
bytes.extend_from_slice(&8u32.to_le_bytes()); bytes.extend_from_slice(&(values.len() as u64).to_le_bytes());
for &val in values {
bytes.extend_from_slice(&(val.len() as u64).to_le_bytes());
bytes.extend_from_slice(val.as_bytes());
}
self.metadata.push((key.to_string(), 9, bytes));
self
}
#[must_use]
pub fn add_i32_array(mut self, key: &str, values: &[i32]) -> Self {
let mut bytes = Vec::new();
bytes.extend_from_slice(&5u32.to_le_bytes()); bytes.extend_from_slice(&(values.len() as u64).to_le_bytes());
for &val in values {
bytes.extend_from_slice(&val.to_le_bytes());
}
self.metadata.push((key.to_string(), 9, bytes));
self
}
#[must_use]
pub fn add_bool_array(mut self, key: &str, values: &[bool]) -> Self {
let mut bytes = Vec::new();
bytes.extend_from_slice(&7u32.to_le_bytes()); bytes.extend_from_slice(&(values.len() as u64).to_le_bytes());
bytes.extend(values.iter().map(|&v| u8::from(v)));
self.metadata.push((key.to_string(), 9, bytes));
self
}
#[must_use]
pub fn build(self) -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&(self.tensors.len() as u64).to_le_bytes());
data.extend_from_slice(&(self.metadata.len() as u64).to_le_bytes());
for (key, value_type, value_bytes) in &self.metadata {
data.extend_from_slice(&(key.len() as u64).to_le_bytes());
data.extend_from_slice(key.as_bytes());
data.extend_from_slice(&value_type.to_le_bytes());
data.extend_from_slice(value_bytes);
}
let mut tensor_data_offset = 0u64;
for (name, dims, qtype, tensor_bytes) in &self.tensors {
data.extend_from_slice(&(name.len() as u64).to_le_bytes());
data.extend_from_slice(name.as_bytes());
data.extend_from_slice(&(dims.len() as u32).to_le_bytes());
for dim in dims.iter().rev() {
data.extend_from_slice(&dim.to_le_bytes());
}
data.extend_from_slice(&qtype.to_le_bytes());
data.extend_from_slice(&tensor_data_offset.to_le_bytes());
tensor_data_offset += tensor_bytes.len() as u64;
}
let current_len = data.len();
let aligned = current_len.div_ceil(GGUF_ALIGNMENT) * GGUF_ALIGNMENT;
data.resize(aligned, 0);
for (_, _, _, tensor_bytes) in &self.tensors {
data.extend_from_slice(tensor_bytes);
}
data
}
}
include!("test_factory_create.rs");
include!("test_factory_build_minimal.rs");
include!("test_factory_gguf_builder.rs");