use axonml_core::Result;
use axonml_nn::Module;
use axonml_tensor::Tensor;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TensorData {
pub shape: Vec<usize>,
pub values: Vec<f32>,
}
impl TensorData {
#[must_use]
pub fn from_tensor(tensor: &Tensor<f32>) -> Self {
Self {
shape: tensor.shape().to_vec(),
values: tensor.to_vec(),
}
}
pub fn to_tensor(&self) -> Result<Tensor<f32>> {
Tensor::from_vec(self.values.clone(), &self.shape)
}
#[must_use]
pub fn numel(&self) -> usize {
self.values.len()
}
#[must_use]
pub fn shape(&self) -> &[usize] {
&self.shape
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StateDictEntry {
pub data: TensorData,
pub requires_grad: bool,
#[serde(default)]
pub metadata: HashMap<String, String>,
}
impl StateDictEntry {
#[must_use]
pub fn new(data: TensorData, requires_grad: bool) -> Self {
Self {
data,
requires_grad,
metadata: HashMap::new(),
}
}
#[must_use]
pub fn with_metadata(mut self, key: &str, value: &str) -> Self {
self.metadata.insert(key.to_string(), value.to_string());
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct StateDict {
entries: HashMap<String, StateDictEntry>,
#[serde(default)]
metadata: HashMap<String, String>,
}
impl StateDict {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn from_module<M: Module>(module: &M) -> Self {
let mut state_dict = Self::new();
let named = module.named_parameters();
if named.is_empty() {
for (i, param) in module.parameters().iter().enumerate() {
let name = format!("param_{i}");
let tensor_data = TensorData::from_tensor(¶m.data());
let entry = StateDictEntry::new(tensor_data, param.requires_grad());
state_dict.entries.insert(name, entry);
}
} else {
for (name, param) in named {
let tensor_data = TensorData::from_tensor(¶m.data());
let entry = StateDictEntry::new(tensor_data, param.requires_grad());
state_dict.entries.insert(name, entry);
}
}
state_dict
}
pub fn insert(&mut self, name: String, data: TensorData) {
let entry = StateDictEntry::new(data, true);
self.entries.insert(name, entry);
}
pub fn insert_entry(&mut self, name: String, entry: StateDictEntry) {
self.entries.insert(name, entry);
}
#[must_use]
pub fn get(&self, name: &str) -> Option<&StateDictEntry> {
self.entries.get(name)
}
pub fn get_mut(&mut self, name: &str) -> Option<&mut StateDictEntry> {
self.entries.get_mut(name)
}
#[must_use]
pub fn contains(&self, name: &str) -> bool {
self.entries.contains_key(name)
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn keys(&self) -> impl Iterator<Item = &String> {
self.entries.keys()
}
pub fn entries(&self) -> impl Iterator<Item = (&String, &StateDictEntry)> {
self.entries.iter()
}
pub fn remove(&mut self, name: &str) -> Option<StateDictEntry> {
self.entries.remove(name)
}
pub fn merge(&mut self, other: StateDict) {
for (name, entry) in other.entries {
self.entries.insert(name, entry);
}
}
#[must_use]
pub fn filter_prefix(&self, prefix: &str) -> StateDict {
let mut filtered = StateDict::new();
for (name, entry) in &self.entries {
if name.starts_with(prefix) {
filtered.entries.insert(name.clone(), entry.clone());
}
}
filtered
}
#[must_use]
pub fn strip_prefix(&self, prefix: &str) -> StateDict {
let mut stripped = StateDict::new();
for (name, entry) in &self.entries {
let new_name = name.strip_prefix(prefix).unwrap_or(name).to_string();
stripped.entries.insert(new_name, entry.clone());
}
stripped
}
#[must_use]
pub fn add_prefix(&self, prefix: &str) -> StateDict {
let mut prefixed = StateDict::new();
for (name, entry) in &self.entries {
let new_name = format!("{prefix}{name}");
prefixed.entries.insert(new_name, entry.clone());
}
prefixed
}
pub fn set_metadata(&mut self, key: &str, value: &str) {
self.metadata.insert(key.to_string(), value.to_string());
}
#[must_use]
pub fn get_metadata(&self, key: &str) -> Option<&String> {
self.metadata.get(key)
}
#[must_use]
pub fn total_params(&self) -> usize {
self.entries.values().map(|e| e.data.numel()).sum()
}
#[must_use]
pub fn size_bytes(&self) -> usize {
self.total_params() * std::mem::size_of::<f32>()
}
#[must_use]
pub fn summary(&self) -> String {
let mut lines = Vec::new();
lines.push(format!("StateDict with {} entries:", self.len()));
lines.push(format!(" Total parameters: {}", self.total_params()));
lines.push(format!(" Size: {} bytes", self.size_bytes()));
lines.push(" Entries:".to_string());
for (name, entry) in &self.entries {
lines.push(format!(
" {} - shape: {:?}, numel: {}",
name,
entry.data.shape,
entry.data.numel()
));
}
lines.join("\n")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tensor_data_roundtrip() {
let original = Tensor::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[2, 3]).unwrap();
let data = TensorData::from_tensor(&original);
let restored = data.to_tensor().unwrap();
assert_eq!(original.shape(), restored.shape());
assert_eq!(original.to_vec(), restored.to_vec());
}
#[test]
fn test_state_dict_operations() {
let mut state_dict = StateDict::new();
let data1 = TensorData {
shape: vec![10, 5],
values: vec![0.0; 50],
};
let data2 = TensorData {
shape: vec![5],
values: vec![0.0; 5],
};
state_dict.insert("linear.weight".to_string(), data1);
state_dict.insert("linear.bias".to_string(), data2);
assert_eq!(state_dict.len(), 2);
assert_eq!(state_dict.total_params(), 55);
assert!(state_dict.contains("linear.weight"));
assert!(state_dict.contains("linear.bias"));
}
#[test]
fn test_state_dict_filter_prefix() {
let mut state_dict = StateDict::new();
state_dict.insert(
"encoder.layer1.weight".to_string(),
TensorData {
shape: vec![10],
values: vec![0.0; 10],
},
);
state_dict.insert(
"encoder.layer1.bias".to_string(),
TensorData {
shape: vec![10],
values: vec![0.0; 10],
},
);
state_dict.insert(
"decoder.layer1.weight".to_string(),
TensorData {
shape: vec![10],
values: vec![0.0; 10],
},
);
let encoder_dict = state_dict.filter_prefix("encoder.");
assert_eq!(encoder_dict.len(), 2);
assert!(encoder_dict.contains("encoder.layer1.weight"));
}
#[test]
fn test_state_dict_strip_prefix() {
let mut state_dict = StateDict::new();
state_dict.insert(
"model.linear.weight".to_string(),
TensorData {
shape: vec![10],
values: vec![0.0; 10],
},
);
let stripped = state_dict.strip_prefix("model.");
assert!(stripped.contains("linear.weight"));
}
#[test]
fn test_state_dict_merge() {
let mut dict1 = StateDict::new();
dict1.insert(
"a".to_string(),
TensorData {
shape: vec![1],
values: vec![1.0],
},
);
let mut dict2 = StateDict::new();
dict2.insert(
"b".to_string(),
TensorData {
shape: vec![1],
values: vec![2.0],
},
);
dict1.merge(dict2);
assert_eq!(dict1.len(), 2);
assert!(dict1.contains("a"));
assert!(dict1.contains("b"));
}
#[test]
fn test_state_dict_summary() {
let mut state_dict = StateDict::new();
state_dict.insert(
"weight".to_string(),
TensorData {
shape: vec![10, 5],
values: vec![0.0; 50],
},
);
let summary = state_dict.summary();
assert!(summary.contains("1 entries"));
assert!(summary.contains("50"));
}
}