blob: 54efb613dc52c7ffe1898fc417081c7ecb837124 [file] [log] [blame]
#include "test.h"
#include "cereal/archives/portable_binary.hpp"
CASE("serialization/undefined") {
auto x = at::Tensor();
EXPECT(!x.defined());
auto y = at::CPU(at::kFloat).randn({5});
std::stringstream ss;
save(ss, &x);
load(ss, &y);
EXPECT(!y.defined());
}
CASE("serialization/cputypes") {
for (int i = 0; i < static_cast<int>(at::ScalarType::NumOptions); i++) {
if (i == static_cast<int>(at::ScalarType::Half)) {
// XXX can't serialize half tensors at the moment since contiguous() is
// not implemented for this type;
continue;
} else if (i == static_cast<int>(at::ScalarType::Undefined)) {
// We can't construct a tensor for this type. This is tested in
// serialization/undefined anyway.
continue;
}
auto x =
at::getType(at::kCPU, static_cast<at::ScalarType>(i)).ones({5, 5});
auto y = at::Tensor();
std::stringstream ss;
save(ss, &x);
load(ss, &y);
EXPECT(y.defined());
EXPECT(x.sizes().vec() == y.sizes().vec());
if (at::isIntegralType(static_cast<at::ScalarType>(i))) {
EXPECT(x.equal(y));
} else {
EXPECT(x.allclose(y));
}
}
}
CASE("serialization/binary") {
auto x = at::CPU(at::kFloat).randn({5, 5});
auto y = at::Tensor();
std::stringstream ss;
{
cereal::BinaryOutputArchive archive(ss);
archive(x);
}
{
cereal::BinaryInputArchive archive(ss);
archive(y);
}
EXPECT(y.defined());
EXPECT(x.sizes().vec() == y.sizes().vec());
EXPECT(x.allclose(y));
}
CASE("serialization/portable_binary") {
auto x = at::CPU(at::kFloat).randn({5, 5});
auto y = at::Tensor();
std::stringstream ss;
{
cereal::PortableBinaryOutputArchive archive(ss);
archive(x);
}
{
cereal::PortableBinaryInputArchive archive(ss);
archive(y);
}
EXPECT(y.defined());
EXPECT(x.sizes().vec() == y.sizes().vec());
EXPECT(x.allclose(y));
}
CASE("serialization/resized") {
auto x = at::CPU(at::kFloat).randn({11, 5});
x.resize_({5, 5});
auto y = at::Tensor();
std::stringstream ss;
{
cereal::BinaryOutputArchive archive(ss);
archive(x);
}
{
cereal::BinaryInputArchive archive(ss);
archive(y);
}
EXPECT(y.defined());
EXPECT(x.sizes().vec() == y.sizes().vec());
EXPECT(x.allclose(y));
}
CASE("serialization/sliced") {
auto x = at::CPU(at::kFloat).randn({11, 5});
x = x.slice(0, 1, 3);
auto y = at::Tensor();
std::stringstream ss;
{
cereal::BinaryOutputArchive archive(ss);
archive(x);
}
{
cereal::BinaryInputArchive archive(ss);
archive(y);
}
EXPECT(y.defined());
EXPECT(x.sizes().vec() == y.sizes().vec());
EXPECT(x.allclose(y));
}
CASE("serialization/noncontig") {
auto x = at::CPU(at::kFloat).randn({11, 5});
x = x.slice(1, 1, 4);
auto y = at::Tensor();
std::stringstream ss;
{
cereal::BinaryOutputArchive archive(ss);
archive(x);
}
{
cereal::BinaryInputArchive archive(ss);
archive(y);
}
EXPECT(y.defined());
EXPECT(x.sizes().vec() == y.sizes().vec());
EXPECT(x.allclose(y));
}
CASE("serialization/xor") {
// We better be able to save and load a XOR model!
auto makeModel = []() {
return ContainerList()
.append(Linear(2, 8).make())
.append(Linear(8, 1).make())
.make();
};
auto getLoss = [](std::shared_ptr<ContainerList> model, uint32_t bs) {
auto inp = at::CPU(at::kFloat).tensor({bs, 2});
auto lab = at::CPU(at::kFloat).tensor({bs});
for (auto i = 0U; i < bs; i++) {
auto a = std::rand() % 2;
auto b = std::rand() % 2;
auto c = a ^ b;
inp[i][0] = a;
inp[i][1] = b;
lab[i] = c;
}
// forward
auto x = Var(inp);
auto y = Var(lab, false);
for (auto layer : *model) x = layer->forward({x})[0].sigmoid_();
return at::binary_cross_entropy(x, y);
};
auto model = makeModel();
auto model2 = makeModel();
auto model3 = makeModel();
auto optim = SGD(model, 1e-1).momentum(0.9).nesterov().weight_decay(1e-6).make();
float running_loss = 1;
int epoch = 0;
while (running_loss > 0.1) {
Variable loss = getLoss(model, 4);
optim->zero_grad();
backward(loss);
optim->step();
running_loss = running_loss * 0.99 + loss.data().sum().toCFloat() * 0.01;
EXPECT(epoch < 3000);
epoch++;
}
std::stringstream ss;
save(ss, model);
load(ss, model2);
auto loss = getLoss(model2, 100);
EXPECT(loss.toCFloat() < 0.1);
CUDA_GUARD;
model2->cuda();
ss.clear();
save(ss, model2);
load(ss, model3);
loss = getLoss(model3, 100);
EXPECT(loss.toCFloat() < 0.1);
}
CASE("serialization/optim") {
auto model1 = Linear(5, 2).make();
auto model2 = Linear(5, 2).make();
auto model3 = Linear(5, 2).make();
// Models 1, 2, 3 will have the same params
std::stringstream ss;
save(ss, model1);
load(ss, model2);
ss.seekg(0, std::ios::beg);
load(ss, model3);
// Make some optimizers with momentum (and thus state)
auto optim1 = SGD(model1, 1e-1).momentum(0.9).make();
auto optim2 = SGD(model2, 1e-1).momentum(0.9).make();
auto optim2_2 = SGD(model2, 1e-1).momentum(0.9).make();
auto optim3 = SGD(model3, 1e-1).momentum(0.9).make();
auto optim3_2 = SGD(model3, 1e-1).momentum(0.9).make();
auto x = Var(at::CPU(at::kFloat).ones({10, 5}), true);
auto step = [&](Optimizer optim, Container model) {
optim->zero_grad();
auto y = model->forward({x})[0].sum();
backward(y);
optim->step();
};
// Do 2 steps of model1
step(optim1, model1);
step(optim1, model1);
// Do 2 steps of model 2 without saving the optimizer
step(optim2, model2);
step(optim2_2, model2);
// Do 2 steps of model 3 while saving the optimizer
step(optim3, model3);
ss.clear();
save(ss, optim3);
load(ss, optim3_2);
step(optim3_2, model3);
auto param1 = model1->parameters();
auto param2 = model2->parameters();
auto param3 = model3->parameters();
for (auto& p : param1) {
auto name = p.first;
// Model 1 and 3 should be the same
EXPECT(param1[name].norm().toCFloat() == param3[name].norm().toCFloat());
EXPECT(param1[name].norm().toCFloat() != param2[name].norm().toCFloat());
}
}