use oxibonsai_core::gguf::metadata::{MetadataStore, MetadataValue};
use crate::error::{DitError, DitResult};
pub const ARCHITECTURE: &str = "bonsai-image";
pub const DEFAULT_EPS: f32 = 1e-6;
const KEY_ARCHITECTURE: &str = "general.architecture";
const KEY_NUM_LAYERS: &str = "bonsai-image.num_layers";
const KEY_NUM_SINGLE_LAYERS: &str = "bonsai-image.num_single_layers";
const KEY_HEAD_COUNT: &str = "bonsai-image.attention.head_count";
const KEY_HEAD_DIM: &str = "bonsai-image.attention.head_dim";
const KEY_JOINT_ATTENTION_DIM: &str = "bonsai-image.joint_attention_dim";
const KEY_IN_CHANNELS: &str = "bonsai-image.in_channels";
const KEY_MLP_RATIO: &str = "bonsai-image.mlp_ratio";
const KEY_AXES_DIMS_ROPE: &str = "bonsai-image.rope.axes_dims";
const KEY_ROPE_THETA: &str = "bonsai-image.rope.theta";
const KEY_GUIDANCE_EMBEDS: &str = "bonsai-image.guidance_embeds";
#[derive(Debug, Clone, PartialEq)]
pub struct DitConfig {
pub num_layers: u32,
pub num_single_layers: u32,
pub num_attention_heads: u32,
pub attention_head_dim: u32,
pub joint_attention_dim: u32,
pub in_channels: u32,
pub mlp_ratio: f32,
pub axes_dims_rope: Vec<u32>,
pub rope_theta: f32,
pub guidance_embeds: bool,
pub eps: f32,
}
impl DitConfig {
pub fn from_metadata(meta: &MetadataStore) -> DitResult<Self> {
let arch = meta
.get(KEY_ARCHITECTURE)
.and_then(|v| v.as_str())
.ok_or_else(|| DitError::MissingMetadata {
key: KEY_ARCHITECTURE.to_string(),
})?;
if arch != ARCHITECTURE {
return Err(DitError::WrongArchitecture {
found: arch.to_string(),
expected: ARCHITECTURE.to_string(),
});
}
let num_layers = require_u32(meta, KEY_NUM_LAYERS)?;
let num_single_layers = require_u32(meta, KEY_NUM_SINGLE_LAYERS)?;
let num_attention_heads = require_u32(meta, KEY_HEAD_COUNT)?;
let attention_head_dim = require_u32(meta, KEY_HEAD_DIM)?;
let joint_attention_dim = require_u32(meta, KEY_JOINT_ATTENTION_DIM)?;
let in_channels = require_u32(meta, KEY_IN_CHANNELS)?;
let mlp_ratio = require_f32(meta, KEY_MLP_RATIO)?;
let axes_dims_rope = require_u32_array(meta, KEY_AXES_DIMS_ROPE)?;
let rope_theta = require_f32(meta, KEY_ROPE_THETA)?;
let guidance_embeds = require_bool(meta, KEY_GUIDANCE_EMBEDS)?;
Ok(Self {
num_layers,
num_single_layers,
num_attention_heads,
attention_head_dim,
joint_attention_dim,
in_channels,
mlp_ratio,
axes_dims_rope,
rope_theta,
guidance_embeds,
eps: DEFAULT_EPS,
})
}
pub fn hidden_size(&self) -> u32 {
self.num_attention_heads * self.attention_head_dim
}
pub fn ffn_inner_size(&self) -> u32 {
((self.hidden_size() as f32) * self.mlp_ratio).round() as u32
}
pub fn rope_dim(&self) -> u32 {
self.axes_dims_rope.iter().sum()
}
}
fn require_u32(meta: &MetadataStore, key: &str) -> DitResult<u32> {
meta.get(key)
.and_then(|v| v.as_u32())
.ok_or_else(|| missing_or_invalid(meta, key, "expected u32"))
}
fn require_f32(meta: &MetadataStore, key: &str) -> DitResult<f32> {
meta.get(key)
.and_then(|v| v.as_f32())
.ok_or_else(|| missing_or_invalid(meta, key, "expected f32"))
}
fn require_bool(meta: &MetadataStore, key: &str) -> DitResult<bool> {
meta.get(key)
.and_then(|v| v.as_bool())
.ok_or_else(|| missing_or_invalid(meta, key, "expected bool"))
}
fn require_u32_array(meta: &MetadataStore, key: &str) -> DitResult<Vec<u32>> {
let value = meta.get(key).ok_or_else(|| DitError::MissingMetadata {
key: key.to_string(),
})?;
let MetadataValue::Array(items) = value else {
return Err(DitError::InvalidMetadata {
key: key.to_string(),
reason: "expected array".to_string(),
});
};
let mut out = Vec::with_capacity(items.len());
for item in items {
let n = item.as_u32().ok_or_else(|| DitError::InvalidMetadata {
key: key.to_string(),
reason: "array element is not a u32".to_string(),
})?;
out.push(n);
}
Ok(out)
}
fn missing_or_invalid(meta: &MetadataStore, key: &str, reason: &str) -> DitError {
if meta.get(key).is_some() {
DitError::InvalidMetadata {
key: key.to_string(),
reason: reason.to_string(),
}
} else {
DitError::MissingMetadata {
key: key.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hidden_size_and_ffn_derivation() {
let cfg = DitConfig {
num_layers: 5,
num_single_layers: 20,
num_attention_heads: 24,
attention_head_dim: 128,
joint_attention_dim: 7680,
in_channels: 128,
mlp_ratio: 3.0,
axes_dims_rope: vec![32, 32, 32, 32],
rope_theta: 2000.0,
guidance_embeds: false,
eps: DEFAULT_EPS,
};
assert_eq!(cfg.hidden_size(), 3072);
assert_eq!(cfg.ffn_inner_size(), 9216);
assert_eq!(cfg.rope_dim(), 128);
assert_eq!(cfg.eps, 1e-6);
}
}