Caffe2 - C++ API
A deep learning, cross platform ML framework
modules.cpp
1 #include <gtest/gtest.h>
2 
3 #include <torch/nn/module.h>
4 #include <torch/nn/modules/batchnorm.h>
5 #include <torch/nn/modules/conv.h>
6 #include <torch/nn/modules/dropout.h>
7 #include <torch/nn/modules/embedding.h>
8 #include <torch/nn/modules/functional.h>
9 #include <torch/nn/modules/linear.h>
10 #include <torch/types.h>
11 #include <torch/utils.h>
12 
13 #include <test/cpp/api/support.h>
14 
15 using namespace torch::nn;
16 using namespace torch::test;
17 
18 class TestModel : public torch::nn::Module {
19  public:
20  TestModel()
21  : l1(register_module("l1", Linear(10, 3))),
22  l2(register_module("l2", Linear(3, 5))),
23  l3(register_module("l3", Linear(5, 100))) {}
24 
25  Linear l1, l2, l3;
26 };
27 
29  public:
30  NestedModel()
31  : param_(register_parameter("param", torch::empty({3, 2, 21}))),
32  l1(register_module("l1", Linear(5, 20))),
33  t(register_module("test", std::make_shared<TestModel>())) {}
34 
35  torch::Tensor param_;
36  Linear l1;
37  std::shared_ptr<TestModel> t;
38 };
39 
41 
42 TEST_F(ModulesTest, Conv1d) {
43  Conv1d model(Conv1dOptions(3, 2, 3).stride(2));
44  auto x = torch::randn({2, 3, 5}, torch::requires_grad());
45  auto y = model(x);
46  torch::Tensor s = y.sum();
47 
48  s.backward();
49  ASSERT_EQ(y.ndimension(), 3);
50  ASSERT_EQ(s.ndimension(), 0);
51  for (auto i = 0; i < 3; i++) {
52  ASSERT_EQ(y.size(i), 2);
53  }
54 
55  ASSERT_EQ(model->weight.grad().numel(), 3 * 2 * 3);
56 }
57 
58 TEST_F(ModulesTest, Conv2dEven) {
59  Conv2d model(Conv2dOptions(3, 2, 3).stride(2));
60  auto x = torch::randn({2, 3, 5, 5}, torch::requires_grad());
61  auto y = model(x);
62  torch::Tensor s = y.sum();
63 
64  s.backward();
65  ASSERT_EQ(y.ndimension(), 4);
66  ASSERT_EQ(s.ndimension(), 0);
67  for (auto i = 0; i < 4; i++) {
68  ASSERT_EQ(y.size(i), 2);
69  }
70 
71  ASSERT_EQ(model->weight.grad().numel(), 3 * 2 * 3 * 3);
72 }
73 
74 TEST_F(ModulesTest, Conv2dUneven) {
75  Conv2d model(Conv2dOptions(3, 2, {3, 2}).stride({2, 2}));
76  auto x = torch::randn({2, 3, 5, 4}, torch::requires_grad());
77  auto y = model(x);
78  torch::Tensor s = y.sum();
79 
80  s.backward();
81  ASSERT_EQ(y.ndimension(), 4);
82  ASSERT_EQ(s.ndimension(), 0);
83  for (auto i = 0; i < 4; i++) {
84  ASSERT_EQ(y.size(i), 2);
85  }
86 
87  ASSERT_EQ(model->weight.grad().numel(), 3 * 2 * 3 * 2);
88 }
89 
90 TEST_F(ModulesTest, Conv3d) {
91  Conv3d model(Conv3dOptions(3, 2, 3).stride(2));
92  auto x = torch::randn({2, 3, 5, 5, 5}, torch::requires_grad());
93  auto y = model(x);
94  torch::Tensor s = y.sum();
95 
96  s.backward();
97  ASSERT_EQ(y.ndimension(), 5);
98  ASSERT_EQ(s.ndimension(), 0);
99  for (auto i = 0; i < 5; i++) {
100  ASSERT_EQ(y.size(i), 2);
101  }
102 
103  ASSERT_TRUE(model->weight.grad().numel() == 3 * 2 * 3 * 3 * 3);
104 }
105 
106 TEST_F(ModulesTest, Linear) {
107  Linear model(5, 2);
108  auto x = torch::randn({10, 5}, torch::requires_grad());
109  auto y = model(x);
110  torch::Tensor s = y.sum();
111 
112  s.backward();
113  ASSERT_EQ(y.ndimension(), 2);
114  ASSERT_EQ(s.ndimension(), 0);
115  ASSERT_EQ(y.size(0), 10);
116  ASSERT_EQ(y.size(1), 2);
117 
118  ASSERT_EQ(model->weight.grad().numel(), 2 * 5);
119 }
120 
121 TEST_F(ModulesTest, SimpleContainer) {
122  auto model = std::make_shared<SimpleContainer>();
123  auto l1 = model->add(Linear(10, 3), "l1");
124  auto l2 = model->add(Linear(3, 5), "l2");
125  auto l3 = model->add(Linear(5, 100), "l3");
126 
127  auto x = torch::randn({1000, 10}, torch::requires_grad());
128  x = l1(x).clamp_min(0);
129  x = l2(x).clamp_min(0);
130  x = l3(x).clamp_min(0);
131 
132  x.backward();
133  ASSERT_EQ(x.ndimension(), 2);
134  ASSERT_EQ(x.size(0), 1000);
135  ASSERT_EQ(x.size(1), 100);
136  ASSERT_EQ(x.min().item<float>(), 0);
137 }
138 
139 TEST_F(ModulesTest, EmbeddingBasic) {
140  const int64_t dict_size = 10;
141  Embedding model(dict_size, 2);
142  ASSERT_TRUE(model->named_parameters().contains("weight"));
143  ASSERT_EQ(model->weight.ndimension(), 2);
144  ASSERT_EQ(model->weight.size(0), dict_size);
145  ASSERT_EQ(model->weight.size(1), 2);
146 
147  // Cannot get gradients to change indices (input) - only for embedding
148  // params
149  auto x = torch::full({10}, dict_size - 1, torch::kInt64);
150  auto y = model(x);
151  torch::Tensor s = y.sum();
152 
153  s.backward();
154  ASSERT_EQ(y.ndimension(), 2);
155  ASSERT_EQ(s.ndimension(), 0);
156  ASSERT_EQ(y.size(0), 10);
157  ASSERT_EQ(y.size(1), 2);
158 
159  ASSERT_EQ(model->weight.grad().numel(), 2 * dict_size);
160 }
161 
162 TEST_F(ModulesTest, EmbeddingList) {
163  Embedding model(6, 4);
164  auto x = torch::full({2, 3}, 5, torch::kInt64);
165  auto y = model(x);
166  torch::Tensor s = y.sum();
167 
168  s.backward();
169  ASSERT_EQ(y.ndimension(), 3);
170  ASSERT_EQ(y.size(0), 2);
171  ASSERT_EQ(y.size(1), 3);
172  ASSERT_EQ(y.size(2), 4);
173 }
174 
175 TEST_F(ModulesTest, Dropout) {
176  Dropout dropout(0.5);
177  torch::Tensor x = torch::ones(100, torch::requires_grad());
178  torch::Tensor y = dropout(x);
179 
180  y.backward();
181  ASSERT_EQ(y.ndimension(), 1);
182  ASSERT_EQ(y.size(0), 100);
183  ASSERT_LT(y.sum().item<float>(), 130); // Probably
184  ASSERT_GT(y.sum().item<float>(), 70); // Probably
185 
186  dropout->eval();
187  y = dropout(x);
188  ASSERT_EQ(y.sum().item<float>(), 100);
189 }
190 
191 TEST_F(ModulesTest, Parameters) {
192  auto model = std::make_shared<NestedModel>();
193  auto parameters = model->named_parameters();
194  ASSERT_EQ(parameters["param"].size(0), 3);
195  ASSERT_EQ(parameters["param"].size(1), 2);
196  ASSERT_EQ(parameters["param"].size(2), 21);
197  ASSERT_EQ(parameters["l1.bias"].size(0), 20);
198  ASSERT_EQ(parameters["l1.weight"].size(0), 20);
199  ASSERT_EQ(parameters["l1.weight"].size(1), 5);
200  ASSERT_EQ(parameters["test.l1.bias"].size(0), 3);
201  ASSERT_EQ(parameters["test.l1.weight"].size(0), 3);
202  ASSERT_EQ(parameters["test.l1.weight"].size(1), 10);
203  ASSERT_EQ(parameters["test.l2.bias"].size(0), 5);
204  ASSERT_EQ(parameters["test.l2.weight"].size(0), 5);
205  ASSERT_EQ(parameters["test.l2.weight"].size(1), 3);
206  ASSERT_EQ(parameters["test.l3.bias"].size(0), 100);
207  ASSERT_EQ(parameters["test.l3.weight"].size(0), 100);
208  ASSERT_EQ(parameters["test.l3.weight"].size(1), 5);
209 }
210 
211 TEST_F(ModulesTest, FunctionalCallsSuppliedFunction) {
212  bool was_called = false;
213  auto functional = Functional([&was_called](torch::Tensor input) {
214  was_called = true;
215  return input;
216  });
217  auto output = functional(torch::ones(5, torch::requires_grad()));
218  ASSERT_TRUE(was_called);
219  ASSERT_TRUE(output.equal(torch::ones(5, torch::requires_grad())));
220 
221  was_called = false;
222  // Use the call operator overload here.
223  output = functional(torch::ones(5, torch::requires_grad()));
224  ASSERT_TRUE(was_called);
225  ASSERT_TRUE(output.equal(torch::ones(5, torch::requires_grad())));
226 }
227 
228 TEST_F(ModulesTest, FunctionalWithTorchFunction) {
229  auto functional = Functional(torch::relu);
230  ASSERT_EQ(functional(torch::ones({})).item<float>(), 1);
231  ASSERT_EQ(functional(torch::ones({})).item<float>(), 1);
232  ASSERT_EQ(functional(torch::ones({}) * -1).item<float>(), 0);
233 }
234 
235 TEST_F(ModulesTest, FunctionalArgumentBinding) {
236  auto functional =
237  Functional(torch::elu, /*alpha=*/1, /*scale=*/0, /*input_scale=*/1);
238  ASSERT_EQ(functional(torch::ones({})).item<float>(), 0);
239 }
240 
241 TEST_F(ModulesTest, BatchNormStateful) {
242  BatchNorm bn(5);
243 
244  // Is stateful by default.
245  ASSERT_TRUE(bn->options.stateful());
246 
247  ASSERT_TRUE(bn->running_mean.defined());
248  ASSERT_EQ(bn->running_mean.dim(), 1);
249  ASSERT_EQ(bn->running_mean.size(0), 5);
250 
251  ASSERT_TRUE(bn->running_var.defined());
252  ASSERT_EQ(bn->running_var.dim(), 1);
253  ASSERT_EQ(bn->running_var.size(0), 5);
254 
255  // Is affine by default.
256  ASSERT_TRUE(bn->options.affine());
257 
258  ASSERT_TRUE(bn->weight.defined());
259  ASSERT_EQ(bn->weight.dim(), 1);
260  ASSERT_EQ(bn->weight.size(0), 5);
261 
262  ASSERT_TRUE(bn->bias.defined());
263  ASSERT_EQ(bn->bias.dim(), 1);
264  ASSERT_EQ(bn->bias.size(0), 5);
265 }
266 TEST_F(ModulesTest, BatchNormStateless) {
267  BatchNorm bn(BatchNormOptions(5).stateful(false).affine(false));
268 
269  ASSERT_FALSE(bn->running_mean.defined());
270  ASSERT_FALSE(bn->running_var.defined());
271  ASSERT_FALSE(bn->weight.defined());
272  ASSERT_FALSE(bn->bias.defined());
273 
274  ASSERT_THROWS_WITH(
275  bn(torch::ones({2, 5})),
276  "Calling BatchNorm::forward is only permitted "
277  "when the 'stateful' option is true (was false). "
278  "Use BatchNorm::pure_forward instead.");
279 }
280 
281 TEST_F(ModulesTest, BatchNormPureForward) {
282  BatchNorm bn(BatchNormOptions(5).affine(false));
283  bn->eval();
284 
285  // Want to make sure we use the supplied values in `pure_forward` even if
286  // we are stateful.
287  auto input = torch::randn({2, 5});
288  auto mean = torch::randn(5);
289  auto variance = torch::rand(5);
290  auto output = bn->pure_forward(input, mean, variance);
291  auto expected = (input - mean) / torch::sqrt(variance + bn->options.eps());
292  ASSERT_TRUE(output.allclose(expected));
293 }
294 
295 TEST_F(ModulesTest, Linear_CUDA) {
296  Linear model(5, 2);
297  model->to(torch::kCUDA);
298  auto x =
299  torch::randn({10, 5}, torch::device(torch::kCUDA).requires_grad(true));
300  auto y = model(x);
301  torch::Tensor s = y.sum();
302 
303  s.backward();
304  ASSERT_EQ(y.ndimension(), 2);
305  ASSERT_EQ(s.ndimension(), 0);
306  ASSERT_EQ(y.size(0), 10);
307  ASSERT_EQ(y.size(1), 2);
308 
309  ASSERT_EQ(model->weight.grad().numel(), 2 * 5);
310 }
311 
312 TEST_F(ModulesTest, Linear2_CUDA) {
313  Linear model(5, 2);
314  model->to(torch::kCUDA);
315  model->to(torch::kCPU);
316  auto x = torch::randn({10, 5}, torch::requires_grad());
317  auto y = model(x);
318  torch::Tensor s = y.sum();
319 
320  s.backward();
321  ASSERT_EQ(y.ndimension(), 2);
322  ASSERT_EQ(s.ndimension(), 0);
323  ASSERT_EQ(y.size(0), 10);
324  ASSERT_EQ(y.size(1), 2);
325 
326  ASSERT_EQ(model->weight.grad().numel(), 2 * 5);
327 }
328 
329 TEST_F(ModulesTest, PrettyPrintLinear) {
330  ASSERT_EQ(
331  c10::str(Linear(3, 4)), "torch::nn::Linear(in=3, out=4, with_bias=true)");
332 }
333 
334 TEST_F(ModulesTest, PrettyPrintConv) {
335  ASSERT_EQ(
336  c10::str(Conv1d(3, 4, 5)),
337  "torch::nn::Conv1d(input_channels=3, output_channels=4, kernel_size=5, stride=1)");
338  ASSERT_EQ(
339  c10::str(Conv2d(3, 4, 5)),
340  "torch::nn::Conv2d(input_channels=3, output_channels=4, kernel_size=[5, 5], stride=[1, 1])");
341  ASSERT_EQ(
342  c10::str(Conv2d(Conv2dOptions(3, 4, 5).stride(2))),
343  "torch::nn::Conv2d(input_channels=3, output_channels=4, kernel_size=[5, 5], stride=[2, 2])");
344 
345  const auto options = Conv2dOptions(3, 4, torch::IntArrayRef{5, 6}).stride({1, 2});
346  ASSERT_EQ(
347  c10::str(Conv2d(options)),
348  "torch::nn::Conv2d(input_channels=3, output_channels=4, kernel_size=[5, 6], stride=[1, 2])");
349 }
350 
351 TEST_F(ModulesTest, PrettyPrintDropout) {
352  ASSERT_EQ(c10::str(Dropout(0.5)), "torch::nn::Dropout(rate=0.5)");
353  ASSERT_EQ(
354  c10::str(FeatureDropout(0.5)), "torch::nn::FeatureDropout(rate=0.5)");
355 }
356 
357 TEST_F(ModulesTest, PrettyPrintFunctional) {
358  ASSERT_EQ(c10::str(Functional(torch::relu)), "torch::nn::Functional()");
359 }
360 
361 TEST_F(ModulesTest, PrettyPrintBatchNorm) {
362  ASSERT_EQ(
363  c10::str(BatchNorm(
364  BatchNormOptions(4).eps(0.5).momentum(0.1).affine(false).stateful(
365  true))),
366  "torch::nn::BatchNorm(features=4, eps=0.5, momentum=0.1, affine=false, stateful=true)");
367 }
368 
369 TEST_F(ModulesTest, PrettyPrintEmbedding) {
370  ASSERT_EQ(
371  c10::str(Embedding(10, 2)),
372  "torch::nn::Embedding(count=10, dimension=2)");
373 }
374 
375 TEST_F(ModulesTest, PrettyPrintNestedModel) {
376  struct InnerTestModule : torch::nn::Module {
377  InnerTestModule()
378  : torch::nn::Module("InnerTestModule"),
379  fc(register_module("fc", torch::nn::Linear(3, 4))),
380  table(register_module("table", torch::nn::Embedding(10, 2))) {}
381 
382  torch::nn::Linear fc;
383  torch::nn::Embedding table;
384  };
385 
386  struct TestModule : torch::nn::Module {
387  TestModule()
388  : torch::nn::Module("TestModule"),
389  fc(register_module("fc", torch::nn::Linear(4, 5))),
390  table(register_module("table", torch::nn::Embedding(10, 2))),
391  inner(register_module("inner", std::make_shared<InnerTestModule>())) {
392  }
393 
394  torch::nn::Linear fc;
395  torch::nn::Embedding table;
396  std::shared_ptr<InnerTestModule> inner;
397  };
398 
399  ASSERT_EQ(
400  c10::str(TestModule{}),
401  "TestModule(\n"
402  " (fc): torch::nn::Linear(in=4, out=5, with_bias=true)\n"
403  " (table): torch::nn::Embedding(count=10, dimension=2)\n"
404  " (inner): InnerTestModule(\n"
405  " (fc): torch::nn::Linear(in=3, out=4, with_bias=true)\n"
406  " (table): torch::nn::Embedding(count=10, dimension=2)\n"
407  " )\n"
408  ")");
409 }
void backward(c10::optional< Tensor > gradient=c10::nullopt, bool keep_graph=false, bool create_graph=false)
Computes the gradient of current tensor w.r.t. graph leaves.
Definition: TensorMethods.h:49
The base class for all modules in PyTorch.
Definition: module.h:62
Options for the BatchNorm module.
Definition: batchnorm.h:13
Options for a D-dimensional convolution module.
Definition: conv.h:16