use crate::{
Activation, Linear, Module, Segment, Sequential, Symbol, Tape, Tensor, Value, Visitor,
};
use super::{named_restore, named_snapshot, restore, snapshot};
fn model(tape: &Tape<f64>, fill: f64) -> Sequential<f64> {
Sequential::new()
.then(Linear::new(
tape,
Tensor::filled([2, 3], fill),
Tensor::filled([3], fill),
))
.then(Activation::Tanh)
.then(Linear::new(
tape,
Tensor::filled([3, 1], fill),
Tensor::filled([1], fill),
))
}
#[test]
fn positional_checkpoints_round_trip() {
let input_shape = [1, 2];
let trained_tape = Tape::new();
let trained = model(&trained_tape, 0.5);
let trained_input = trained_tape.leaf(Tensor::filled(input_shape, 1.0_f64));
let trained_output = trained.express(trained_input).symbol();
let trained_network = trained_tape.into_network();
let trained_parameters = trained_network.parameters();
let payloads = snapshot(&trained_parameters, &trained);
assert_eq!(payloads.len(), 4);
let fresh_tape = Tape::new();
let fresh = model(&fresh_tape, 0.0);
let fresh_input = fresh_tape.leaf(Tensor::filled(input_shape, 1.0_f64));
let fresh_output = fresh.express(fresh_input).symbol();
let fresh_network = fresh_tape.into_network();
let restored = restore(&fresh_network.parameters(), &fresh, payloads);
assert_eq!(
trained_network
.forward(&trained_parameters, [])
.of(trained_output)
.to_vec(),
fresh_network
.forward(&restored, [])
.of(fresh_output)
.to_vec(),
);
}
#[test]
#[should_panic(expected = "payloads but the module has")]
fn positional_restore_rejects_a_count_mismatch() {
let tape = Tape::new();
let module = model(&tape, 0.0);
let parameters = tape.into_network().parameters();
let _ = restore(¶meters, &module, vec![Tensor::filled([2, 3], 1.0_f64)]);
}
#[test]
fn named_checkpoints_round_trip() {
let trained_tape = Tape::new();
let trained = model(&trained_tape, 0.25);
let trained_parameters = trained_tape.into_network().parameters();
let entries = named_snapshot(&trained_parameters, &trained);
let rendered: Vec<String> = entries.iter().map(|(path, _)| path.to_string()).collect();
assert_eq!(rendered, ["0.weights", "0.bias", "2.weights", "2.bias"]);
let fresh_tape = Tape::new();
let fresh = model(&fresh_tape, 0.0);
let fresh_parameters = fresh_tape.into_network().parameters();
let restored = named_restore(&fresh_parameters, &fresh, entries);
let payloads = snapshot(&restored, &fresh);
assert_eq!(payloads[0].to_vec(), vec![0.25; 6]);
}
#[test]
#[should_panic(expected = "missing entries for: 2.weights")]
fn named_restore_rejects_missing_entries() {
let tape = Tape::new();
let module = model(&tape, 0.5);
let parameters = tape.into_network().parameters();
let mut entries = named_snapshot(¶meters, &module);
entries.remove(2);
let _ = named_restore(¶meters, &module, entries);
}
#[test]
#[should_panic(expected = "no parameter matches")]
fn named_restore_rejects_unexpected_entries() {
let tape = Tape::new();
let first = model(&tape, 0.5);
let second = Sequential::new().then(Linear::new(
&tape,
Tensor::filled([2, 3], 0.5_f64),
Tensor::filled([3], 0.5),
));
let parameters = tape.into_network().parameters();
let entries = named_snapshot(¶meters, &first);
let _ = named_restore(¶meters, &second, entries);
}
#[test]
fn tied_parameters_restore_once() {
struct Tied(Symbol);
impl Module<f64> for Tied {
fn express<'tape>(&self, input: Value<'tape, f64>) -> Value<'tape, f64> {
input.tape().resolve(self.0)
}
fn visit(&self, visitor: &mut dyn Visitor) {
visitor.enter(Segment::Name("embedding"));
visitor.parameter("weights", self.0);
visitor.leave();
visitor.enter(Segment::Name("head"));
visitor.parameter("weights", self.0);
visitor.leave();
}
}
let tape = Tape::new();
let table = tape.parameter(Tensor::filled([2, 2], 0.5_f64)).symbol();
let model = Tied(table);
let parameters = tape.into_network().parameters();
let entries = named_snapshot(¶meters, &model);
assert_eq!(entries.len(), 2);
let restored = named_restore(¶meters, &model, entries);
assert_eq!(restored.of(table).to_vec(), vec![0.5; 4]);
}