use crate::optim::Var;
use crate::varmap::VarMap;
use fuel_ir::Shape;
use fuel_ir::error::Result;
use fuel_tensor::{DType, Device};
#[derive(Clone, Debug)]
pub struct VarBuilder {
map: VarMap,
prefix: String,
dtype: DType,
device: Device,
}
impl VarBuilder {
pub fn from_varmap(map: VarMap, default_dtype: DType, default_device: Device) -> Self {
Self {
map,
prefix: String::new(),
dtype: default_dtype,
device: default_device,
}
}
pub fn pp(&self, name: impl AsRef<str>) -> Self {
let segment = name.as_ref();
let new_prefix = if self.prefix.is_empty() {
segment.to_string()
} else {
format!("{}.{}", self.prefix, segment)
};
Self {
map: self.map.clone(),
prefix: new_prefix,
dtype: self.dtype,
device: self.device.clone(),
}
}
pub fn get(&self, shape: impl Into<Shape>, name: &str) -> Result<Var> {
self.get_with(shape, name, |s| vec![0.0_f32; s.elem_count()])
}
pub fn get_with(
&self,
shape: impl Into<Shape>,
name: &str,
init_fn: impl FnOnce(&Shape) -> Vec<f32>,
) -> Result<Var> {
let shape = shape.into();
let key = self.path(name);
if let Some(existing) = self.map.get(&key) {
if existing.shape().dims() != shape.dims() {
return Err(fuel_ir::error::Error::Msg(format!(
"VarBuilder::get_with: parameter {key} already \
registered with shape {:?}, requested {:?}",
existing.shape().dims(),
shape.dims(),
))
.bt());
}
return Ok(existing);
}
let data = init_fn(&shape);
let var = Var::new(key, shape, data)?;
self.map.insert(var.clone());
Ok(var)
}
pub fn map(&self) -> &VarMap {
&self.map
}
pub fn prefix(&self) -> &str {
&self.prefix
}
pub fn dtype(&self) -> DType {
self.dtype
}
pub fn device(&self) -> &Device {
&self.device
}
fn path(&self, name: &str) -> String {
if self.prefix.is_empty() {
name.to_string()
} else {
format!("{}.{}", self.prefix, name)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pp_chains_produce_dot_joined_keys() -> Result<()> {
let map = VarMap::new();
let vs = VarBuilder::from_varmap(map.clone(), DType::F32, Device::cpu());
let v = vs
.pp("outer")
.pp("inner")
.get(Shape::from_dims(&[2, 3]), "w")?;
assert_eq!(v.name(), "outer.inner.w");
assert_eq!(v.shape().dims(), &[2, 3]);
assert!(map.get("outer.inner.w").is_some());
let v2 = vs.get(Shape::from_dims(&[4]), "bare")?;
assert_eq!(v2.name(), "bare");
Ok(())
}
#[test]
fn repeated_get_returns_same_var() -> Result<()> {
let map = VarMap::new();
let vs = VarBuilder::from_varmap(map, DType::F32, Device::cpu());
let first = vs.get_with(Shape::from_dims(&[3]), "w", |s| {
(0..s.elem_count()).map(|i| i as f32 + 1.0).collect()
})?;
let second = vs.get_with(Shape::from_dims(&[3]), "w", |_| vec![99.0; 3])?;
assert_eq!(first.name(), second.name());
assert_eq!(first.shape().dims(), second.shape().dims());
assert_eq!(first.to_vec(), vec![1.0_f32, 2.0, 3.0]);
assert_eq!(second.to_vec(), vec![1.0_f32, 2.0, 3.0]);
Ok(())
}
#[test]
fn shape_mismatch_on_existing_var_errors() {
let map = VarMap::new();
let vs = VarBuilder::from_varmap(map, DType::F32, Device::cpu());
vs.get(Shape::from_dims(&[2, 2]), "w").unwrap();
let err = vs.get(Shape::from_dims(&[3, 3]), "w");
assert!(err.is_err());
}
#[test]
fn dtype_and_device_accessors_round_trip() {
let map = VarMap::new();
let vs = VarBuilder::from_varmap(map, DType::F32, Device::cpu());
assert_eq!(vs.dtype(), DType::F32);
assert!(vs.device().is_cpu());
let nested = vs.pp("layer0");
assert_eq!(nested.dtype(), DType::F32);
assert!(nested.device().is_cpu());
assert_eq!(nested.prefix(), "layer0");
}
#[test]
fn save_then_load_round_trips_through_var_builder() -> Result<()> {
let tmp = std::env::temp_dir().join("fuel_lazy_varbuilder_save_load.safetensors");
let _ = std::fs::remove_file(&tmp);
let src_map = VarMap::new();
let src_vs = VarBuilder::from_varmap(src_map.clone(), DType::F32, Device::cpu());
let src_w = src_vs
.pp("layer0")
.get_with(Shape::from_dims(&[2, 2]), "weight", |_| {
vec![1.0_f32, 2.0, 3.0, 4.0]
})?;
let src_b = src_vs
.pp("layer0")
.get_with(Shape::from_dims(&[2]), "bias", |_| vec![10.0_f32, 20.0])?;
assert_eq!(src_w.name(), "layer0.weight");
assert_eq!(src_b.name(), "layer0.bias");
src_map.save(&tmp)?;
let dst_map = VarMap::new();
let dst_vs = VarBuilder::from_varmap(dst_map.clone(), DType::F32, Device::cpu());
let dst_w = dst_vs
.pp("layer0")
.get(Shape::from_dims(&[2, 2]), "weight")?;
let dst_b = dst_vs.pp("layer0").get(Shape::from_dims(&[2]), "bias")?;
assert_eq!(dst_w.to_vec(), vec![0.0_f32; 4]);
assert_eq!(dst_b.to_vec(), vec![0.0_f32; 2]);
dst_map.load(&tmp)?;
assert_eq!(dst_w.to_vec(), vec![1.0_f32, 2.0, 3.0, 4.0]);
assert_eq!(dst_b.to_vec(), vec![10.0_f32, 20.0]);
std::fs::remove_file(&tmp).ok();
Ok(())
}
}