#include #include #include #include #include #include #include #include #include #include #include using namespace torch::nn; using namespace torch::test; class TestModel : public torch::nn::Module { public: TestModel() : l1(register_module("l1", Linear(10, 3))), l2(register_module("l2", Linear(3, 5))), l3(register_module("l3", Linear(5, 100))) {} Linear l1, l2, l3; }; class NestedModel : public torch::nn::Module { public: NestedModel() : param_(register_parameter("param", torch::empty({3, 2, 21}))), l1(register_module("l1", Linear(5, 20))), t(register_module("test", std::make_shared())) {} torch::Tensor param_; Linear l1; std::shared_ptr t; }; struct ModulesTest : torch::test::SeedingFixture {}; TEST_F(ModulesTest, Conv1d) { Conv1d model(Conv1dOptions(3, 2, 3).stride(2)); auto x = torch::randn({2, 3, 5}, torch::requires_grad()); auto y = model->forward(x); torch::Tensor s = y.sum(); s.backward(); ASSERT_EQ(y.ndimension(), 3); ASSERT_EQ(s.ndimension(), 0); for (auto i = 0; i < 3; i++) { ASSERT_EQ(y.size(i), 2); } ASSERT_EQ(model->parameters()["weight"].grad().numel(), 3 * 2 * 3); } TEST_F(ModulesTest, Conv2dEven) { Conv2d model(Conv2dOptions(3, 2, 3).stride(2)); auto x = torch::randn({2, 3, 5, 5}, torch::requires_grad()); auto y = model->forward(x); torch::Tensor s = y.sum(); s.backward(); ASSERT_EQ(y.ndimension(), 4); ASSERT_EQ(s.ndimension(), 0); for (auto i = 0; i < 4; i++) { ASSERT_EQ(y.size(i), 2); } ASSERT_EQ(model->parameters()["weight"].grad().numel(), 3 * 2 * 3 * 3); } TEST_F(ModulesTest, Conv2dUneven) { Conv2d model(Conv2dOptions(3, 2, {3, 2}).stride({2, 2})); auto x = torch::randn({2, 3, 5, 4}, torch::requires_grad()); auto y = model->forward(x); torch::Tensor s = y.sum(); s.backward(); ASSERT_EQ(y.ndimension(), 4); ASSERT_EQ(s.ndimension(), 0); for (auto i = 0; i < 4; i++) { ASSERT_EQ(y.size(i), 2); } ASSERT_EQ(model->parameters()["weight"].grad().numel(), 3 * 2 * 3 * 2); } TEST_F(ModulesTest, Conv3d) { Conv3d model(Conv3dOptions(3, 2, 3).stride(2)); auto x = torch::randn({2, 3, 5, 5, 5}, torch::requires_grad()); auto y = model->forward(x); torch::Tensor s = y.sum(); s.backward(); ASSERT_EQ(y.ndimension(), 5); ASSERT_EQ(s.ndimension(), 0); for (auto i = 0; i < 5; i++) { ASSERT_EQ(y.size(i), 2); } ASSERT_TRUE( model->parameters()["weight"].grad().numel() == 3 * 2 * 3 * 3 * 3); } TEST_F(ModulesTest, Linear) { Linear model(5, 2); auto x = torch::randn({10, 5}, torch::requires_grad()); auto y = model->forward(x); torch::Tensor s = y.sum(); s.backward(); ASSERT_EQ(y.ndimension(), 2); ASSERT_EQ(s.ndimension(), 0); ASSERT_EQ(y.size(0), 10); ASSERT_EQ(y.size(1), 2); ASSERT_EQ(model->parameters()["weight"].grad().numel(), 2 * 5); } TEST_F(ModulesTest, SimpleContainer) { auto model = std::make_shared(); auto l1 = model->add(Linear(10, 3), "l1"); auto l2 = model->add(Linear(3, 5), "l2"); auto l3 = model->add(Linear(5, 100), "l3"); auto x = torch::randn({1000, 10}, torch::requires_grad()); x = l1->forward(x).clamp_min(0); x = l2->forward(x).clamp_min(0); x = l3->forward(x).clamp_min(0); x.backward(); ASSERT_EQ(x.ndimension(), 2); ASSERT_EQ(x.size(0), 1000); ASSERT_EQ(x.size(1), 100); ASSERT_EQ(x.min().item(), 0); } TEST_F(ModulesTest, EmbeddingBasic) { const int64_t dict_size = 10; Embedding model(dict_size, 2); ASSERT_TRUE(model->parameters().contains("weight")); ASSERT_EQ(model->weight.ndimension(), 2); ASSERT_EQ(model->weight.size(0), dict_size); ASSERT_EQ(model->weight.size(1), 2); // Cannot get gradients to change indices (input) - only for embedding // params auto x = torch::full({10}, dict_size - 1, torch::kInt64); auto y = model->forward(x); torch::Tensor s = y.sum(); s.backward(); ASSERT_EQ(y.ndimension(), 2); ASSERT_EQ(s.ndimension(), 0); ASSERT_EQ(y.size(0), 10); ASSERT_EQ(y.size(1), 2); ASSERT_EQ(model->parameters()["weight"].grad().numel(), 2 * dict_size); } TEST_F(ModulesTest, EmbeddingList) { Embedding model(6, 4); auto x = torch::full({2, 3}, 5, torch::kInt64); auto y = model->forward(x); torch::Tensor s = y.sum(); s.backward(); ASSERT_EQ(y.ndimension(), 3); ASSERT_EQ(y.size(0), 2); ASSERT_EQ(y.size(1), 3); ASSERT_EQ(y.size(2), 4); } TEST_F(ModulesTest, Dropout) { Dropout dropout(0.5); torch::Tensor x = torch::ones(100, torch::requires_grad()); torch::Tensor y = dropout->forward(x); y.backward(); ASSERT_EQ(y.ndimension(), 1); ASSERT_EQ(y.size(0), 100); ASSERT_LT(y.sum().item(), 130); // Probably ASSERT_GT(y.sum().item(), 70); // Probably dropout->eval(); y = dropout->forward(x); ASSERT_EQ(y.sum().item(), 100); } TEST_F(ModulesTest, Parameters) { auto model = std::make_shared(); auto parameters = model->parameters(); ASSERT_EQ(parameters["param"].size(0), 3); ASSERT_EQ(parameters["param"].size(1), 2); ASSERT_EQ(parameters["param"].size(2), 21); ASSERT_EQ(parameters["l1.bias"].size(0), 20); ASSERT_EQ(parameters["l1.weight"].size(0), 20); ASSERT_EQ(parameters["l1.weight"].size(1), 5); ASSERT_EQ(parameters["test.l1.bias"].size(0), 3); ASSERT_EQ(parameters["test.l1.weight"].size(0), 3); ASSERT_EQ(parameters["test.l1.weight"].size(1), 10); ASSERT_EQ(parameters["test.l2.bias"].size(0), 5); ASSERT_EQ(parameters["test.l2.weight"].size(0), 5); ASSERT_EQ(parameters["test.l2.weight"].size(1), 3); ASSERT_EQ(parameters["test.l3.bias"].size(0), 100); ASSERT_EQ(parameters["test.l3.weight"].size(0), 100); ASSERT_EQ(parameters["test.l3.weight"].size(1), 5); } TEST_F(ModulesTest, FunctionalCallsSuppliedFunction) { bool was_called = false; auto functional = Functional([&was_called](torch::Tensor input) { was_called = true; return input; }); auto output = functional->forward(torch::ones(5, torch::requires_grad())); ASSERT_TRUE(was_called); ASSERT_TRUE(output.equal(torch::ones(5, torch::requires_grad()))); was_called = false; // Use the call operator overload here. output = functional(torch::ones(5, torch::requires_grad())); ASSERT_TRUE(was_called); ASSERT_TRUE(output.equal(torch::ones(5, torch::requires_grad()))); } TEST_F(ModulesTest, FunctionalWithTorchFunction) { auto functional = Functional(torch::relu); ASSERT_EQ(functional(torch::ones({})).item(), 1); ASSERT_EQ(functional(torch::ones({})).item(), 1); ASSERT_EQ(functional(torch::ones({}) * -1).item(), 0); } TEST_F(ModulesTest, FunctionalArgumentBinding) { auto functional = Functional(torch::elu, /*alpha=*/1, /*scale=*/0, /*input_scale=*/1); ASSERT_EQ(functional(torch::ones({})).item(), 0); } TEST_F(ModulesTest, BatchNormStateful) { BatchNorm bn(5); // Is stateful by default. ASSERT_TRUE(bn->options.stateful()); ASSERT_TRUE(bn->running_mean.defined()); ASSERT_EQ(bn->running_mean.dim(), 1); ASSERT_EQ(bn->running_mean.size(0), 5); ASSERT_TRUE(bn->running_variance.defined()); ASSERT_EQ(bn->running_variance.dim(), 1); ASSERT_EQ(bn->running_variance.size(0), 5); // Is affine by default. ASSERT_TRUE(bn->options.affine()); ASSERT_TRUE(bn->weight.defined()); ASSERT_EQ(bn->weight.dim(), 1); ASSERT_EQ(bn->weight.size(0), 5); ASSERT_TRUE(bn->bias.defined()); ASSERT_EQ(bn->bias.dim(), 1); ASSERT_EQ(bn->bias.size(0), 5); } TEST_F(ModulesTest, BatchNormStateless) { BatchNorm bn(BatchNormOptions(5).stateful(false).affine(false)); ASSERT_FALSE(bn->running_mean.defined()); ASSERT_FALSE(bn->running_variance.defined()); ASSERT_FALSE(bn->weight.defined()); ASSERT_FALSE(bn->bias.defined()); ASSERT_THROWS_WITH( bn->forward(torch::ones({2, 5})), "Calling BatchNorm::forward is only permitted " "when the 'stateful' option is true (was false). " "Use BatchNorm::pure_forward instead."); } TEST_F(ModulesTest, BatchNormPureForward) { BatchNorm bn(BatchNormOptions(5).affine(false)); bn->eval(); // Want to make sure we use the supplied values in `pure_forward` even if // we are stateful. auto input = torch::randn({2, 5}); auto mean = torch::randn(5); auto variance = torch::rand(5); auto output = bn->pure_forward(input, mean, variance); auto expected = (input - mean) / torch::sqrt(variance + bn->options.eps()); ASSERT_TRUE(output.allclose(expected)); } TEST_F(ModulesTest, Linear_CUDA) { Linear model(5, 2); model->to(torch::kCUDA); auto x = torch::randn({10, 5}, torch::device(torch::kCUDA).requires_grad(true)); auto y = model->forward(x); torch::Tensor s = y.sum(); s.backward(); ASSERT_EQ(y.ndimension(), 2); ASSERT_EQ(s.ndimension(), 0); ASSERT_EQ(y.size(0), 10); ASSERT_EQ(y.size(1), 2); ASSERT_EQ(model->parameters()["weight"].grad().numel(), 2 * 5); } TEST_F(ModulesTest, Linear2_CUDA) { Linear model(5, 2); model->to(torch::kCUDA); model->to(torch::kCPU); auto x = torch::randn({10, 5}, torch::requires_grad()); auto y = model->forward(x); torch::Tensor s = y.sum(); s.backward(); ASSERT_EQ(y.ndimension(), 2); ASSERT_EQ(s.ndimension(), 0); ASSERT_EQ(y.size(0), 10); ASSERT_EQ(y.size(1), 2); ASSERT_EQ(model->parameters()["weight"].grad().numel(), 2 * 5); }