1/**
2 * Copyright (c) Glow Contributors. See CONTRIBUTORS file.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "BackendTestUtils.h"
18
19#include "glow/ExecutionEngine/ExecutionEngine.h"
20#include "glow/Graph/Graph.h"
21#include "glow/Graph/PlaceholderBindings.h"
22#include "glow/IR/IR.h"
23#include "glow/IR/IRBuilder.h"
24#include "glow/IR/Instrs.h"
25#include "glow/Support/Random.h"
26
27#include "gtest/gtest.h"
28
29#include "llvm/ADT/STLExtras.h"
30
31using namespace glow;
32using llvm::cast;
33
34class BackendCorrectnessTest : public ::testing::TestWithParam<std::string> {
35protected:
36 std::string backendName_{GetParam()};
37};
38
39TEST_P(BackendCorrectnessTest, convTest) {
40 CHECK_IF_ENABLED();
41 PseudoRNG PRNG;
42 Tensor inputs(ElemKind::FloatTy, {20, 41, 32, 6});
43 Tensor kernel(ElemKind::FloatTy, {10, 5, 5, 6});
44 Tensor bias(ElemKind::FloatTy, {10});
45 inputs.getHandle().initXavier(1, PRNG);
46 kernel.getHandle().randomize(-3.0, 3.0, PRNG);
47 bias.getHandle().randomize(-0.5, 0.5, PRNG);
48 std::array<dim_t, 4> S{{20, 15, 12, 10}};
49 llvm::ArrayRef<dim_t> shape(S);
50 Tensor out1(ElemKind::FloatTy, shape);
51 Tensor out2(ElemKind::FloatTy, shape);
52
53 inferConvNet(&inputs, &kernel, &bias, &out1, backendName_);
54 inferConvNet(&inputs, &kernel, &bias, &out2, "Interpreter");
55
56 EXPECT_TRUE(out1.isEqual(out2));
57}
58
59TEST_P(BackendCorrectnessTest, extract3Dtest) {
60 CHECK_IF_ENABLED();
61 PseudoRNG PRNG;
62 Tensor inputs(ElemKind::FloatTy, {5, 100, 100});
63 inputs.getHandle().initXavier(1, PRNG);
64 Tensor out1;
65 Tensor out2;
66
67 inferExtract3D(&inputs, &out1, backendName_);
68 inferExtract3D(&inputs, &out2, "Interpreter");
69
70 EXPECT_TRUE(out1.isEqual(out2));
71}
72
73TEST_P(BackendCorrectnessTest, quantizedConvTest) {
74 CHECK_IF_ENABLED();
75 PseudoRNG PRNG;
76 Tensor inputs(ElemKind::Int8QTy, {20, 41, 32, 6}, 0.025, -7);
77 Tensor kernel(ElemKind::Int8QTy, {10, 5, 5, 6}, 0.003, 3);
78 Tensor bias(ElemKind::Int32QTy, {10}, 0.5, -4);
79 inputs.getHandle<int8_t>().randomize(-128, 127, PRNG);
80 kernel.getHandle<int8_t>().randomize(-128, 127, PRNG);
81 bias.getHandle<int32_t>().randomize(-11, 8, PRNG);
82 std::array<dim_t, 4> S{{20, 15, 12, 10}};
83 llvm::ArrayRef<dim_t> shape(S);
84 Tensor out1(ElemKind::Int8QTy, shape, 0.05, -17);
85 Tensor out2(ElemKind::Int8QTy, shape, 0.05, -17);
86
87 inferConvNet(&inputs, &kernel, &bias, &out1, backendName_);
88 inferConvNet(&inputs, &kernel, &bias, &out2, "Interpreter");
89
90 EXPECT_TRUE(out1.isEqual(out2, 1.0));
91}
92
93TEST_P(BackendCorrectnessTest, convGradTest) {
94 CHECK_IF_ENABLED();
95 PseudoRNG PRNG;
96 Tensor inputs(ElemKind::FloatTy, {9, 8, 5, 4});
97 Tensor kernel1(ElemKind::FloatTy, {3, 5, 3, 4});
98 Tensor bias1(ElemKind::FloatTy, {3});
99 Tensor kernel2(ElemKind::FloatTy, {2, 2, 2, 1});
100 Tensor bias2(ElemKind::FloatTy, {2});
101 Tensor selected(ElemKind::Int64ITy, {9, 1});
102 inputs.getHandle().initXavier(1, PRNG);
103 kernel1.getHandle().randomize(-1.0, 1.4, PRNG);
104 bias1.getHandle().randomize(-0.2, 0.5, PRNG);
105 kernel2.getHandle().randomize(-1.8, 2.3, PRNG);
106 bias2.getHandle().randomize(-0.5, 1.0, PRNG);
107 auto selectedH = selected.getHandle<int64_t>();
108 for (size_t i = 0; i < 9; i++) {
109 selectedH.raw(i) = PRNG.nextRandInt(0, 29);
110 }
111 std::array<dim_t, 4> S1{{9, 6, 10, 1}};
112 llvm::ArrayRef<dim_t> shape1(S1);
113 std::array<dim_t, 2> S2{{9, 30}};
114 llvm::ArrayRef<dim_t> shape2(S2);
115 Tensor out1;
116 Tensor out2;
117
118 trainConvNet(&inputs, &kernel1, &bias1, &kernel2, &bias2, &selected, shape1,
119 shape2, &out1, backendName_);
120 trainConvNet(&inputs, &kernel1, &bias1, &kernel2, &bias2, &selected, shape1,
121 shape2, &out2, "Interpreter");
122
123 EXPECT_TRUE(out1.isEqual(out2));
124}
125
126TEST_P(BackendCorrectnessTest, localResponseNormalizationTest) {
127 CHECK_IF_ENABLED();
128 PseudoRNG PRNG;
129 Tensor inputs(ElemKind::FloatTy, {8, 15, 13, 30});
130 inputs.getHandle().initXavier(1, PRNG);
131 Tensor out1;
132 Tensor out2;
133
134 inferLocalResponseNormalizationNet(&inputs, &out1, backendName_);
135 inferLocalResponseNormalizationNet(&inputs, &out2, "Interpreter");
136
137 EXPECT_TRUE(out1.isEqual(out2));
138}
139
140TEST_P(BackendCorrectnessTest, localResponseNormalizationGradTest) {
141 CHECK_IF_ENABLED();
142 PseudoRNG PRNG;
143 Tensor inputs(ElemKind::FloatTy, {5, 4, 7, 3});
144 Tensor weights(ElemKind::FloatTy, {84, 180});
145 Tensor bias(ElemKind::FloatTy, {180});
146 Tensor selected(ElemKind::Int64ITy, {5, 1});
147 inputs.getHandle().initXavier(1, PRNG);
148 weights.getHandle().randomize(-2.0, 3.0, PRNG);
149 bias.getHandle().randomize(-1.0, 1.3, PRNG);
150 auto selectedH = selected.getHandle<int64_t>();
151 for (size_t i = 0; i < 5; i++) {
152 selectedH.raw(i) = PRNG.nextRandInt(0, 179);
153 }
154 std::array<dim_t, 4> S1{{5, 2, 2, 45}};
155 llvm::ArrayRef<dim_t> shape1(S1);
156 std::array<dim_t, 2> S2{{5, 180}};
157 llvm::ArrayRef<dim_t> shape2(S2);
158 Tensor out1(ElemKind::FloatTy, shape2);
159 Tensor out2(ElemKind::FloatTy, shape1);
160
161 trainLocalResponseNormalizationNet(&inputs, &weights, &bias, &selected,
162 shape1, shape2, &out1, backendName_);
163 trainLocalResponseNormalizationNet(&inputs, &weights, &bias, &selected,
164 shape1, shape2, &out2, "Interpreter");
165
166 EXPECT_TRUE(out1.isEqual(out2));
167}
168
169/// This is a mock backend wrapping the CPU backend. It is used only for unit
170/// testing.
171class MockCPUBackend : public BackendUsingGlowIR {
172 // The actual backend being wrapped.
173 std::unique_ptr<BackendUsingGlowIR> backend_;
174
175public:
176 MockCPUBackend() {
177 backend_.reset(static_cast<BackendUsingGlowIR *>(createBackend("CPU")));
178 }
179
180 std::string getBackendName() const override { return "CPU"; }
181
182 Expected<std::unique_ptr<CompiledFunction>>
183 compile(Function *F, const BackendOptions &opts) const override {
184 return backend_->compile(F, opts);
185 }
186
187 std::unique_ptr<CompiledFunction>
188 compileIR(std::unique_ptr<IRFunction> IR) const override {
189 return backend_->compileIR(std::move(IR));
190 }
191 bool isOpSupported(const NodeInfo &NI) const override { return true; }
192
193 runtime::DeviceManager *
194 createDeviceManager(const runtime::DeviceConfig &deviceConfig) override {
195 return nullptr;
196 }
197};
198
199TEST_P(BackendCorrectnessTest, dataParallelStackingTest) {
200 CHECK_IF_ENABLED();
201 // Create an activation of size 3 and create two overlapping tensorviews of
202 // this activation. Perform data-parallel instructions involving those
203 // tensorviews. The backend's logic for the creation of stacked kernels should
204 // handle them correctly and avoid putting all those data-parallel
205 // instructuins into the same kernel even though they are all
206 // shape-compatible, because some of these instructions would mutate the same
207 // buffer that is already used by other instructions in the stacked kernel.
208 Module mod;
209 Function *F = mod.createFunction("DataParallelStacking");
210 auto M = glow::make_unique<IRFunction>(F);
211
212 auto *var =
213 mod.createPlaceholder(glow::ElemKind::FloatTy, {2}, "output", false);
214 auto ctx = glow::make_unique<ExecutionContext>();
215 auto *outputTensor = ctx->getPlaceholderBindings()->allocate(var);
216 {
217 // Scope the IRBuilder so the active allocations are properly deallocated at
218 // destruction.
219 IRBuilder bb(M.get());
220
221 auto *output = bb.createWeightVar(glow::ElemKind::FloatTy, {2}, "output1",
222 WeightVar::MutabilityKind::Mutable);
223
224 M->getVariableMap()[var] = output;
225
226 auto *act = bb.createAllocActivationInst(
227 "act1", mod.uniqueType(glow::ElemKind::FloatTy, {3}));
228 bb.createSplatInst("zero", act, 0.0);
229 auto tv1 = bb.createTensorViewInst(
230 "tv1", act, mod.uniqueType(glow::ElemKind::FloatTy, {2}), {0});
231 auto tv2 = bb.createTensorViewInst(
232 "tv2", act, mod.uniqueType(glow::ElemKind::FloatTy, {2}), {1});
233
234 auto *one = bb.createAllocActivationInst(
235 "act2", mod.uniqueType(glow::ElemKind::FloatTy, {2}));
236 bb.createSplatInst("one", one, 1.0);
237 // after this instruction:
238 // act will be: [1, 1, 0]
239 // tv1 will be: [1, 1]
240 // tv2 will be: [1, 0]
241 bb.createElementAddInst("elem_add1", tv1, tv1, one);
242 // The next instruction should not be put into the same stacking kernel,
243 // because tv2 overlaps with tv1.
244 // after this instruction:
245 // act will be: [1, 2, 2]
246 // tv1 will be: [1, 2]
247 // tv2 will be: [2, 2]
248 bb.createElementAddInst("elem_add2", tv2, tv2, tv1);
249 // after this instruction:
250 // output will be: [3, 4]
251 // tv1 will be: [1, 2]
252 // tv2 will be: [2, 2]
253 // If stacking would put elem_add1 and elem_add2 into the same stacking
254 // kernel, the output would be: [2, 4], which is wrong.
255 bb.createElementAddInst("elem_add3", output, tv2, tv1);
256 bb.createDeallocActivationInst("dealloc", act);
257 }
258
259 MockCPUBackend backend;
260 auto function = backend.compileIR(std::move(M));
261 ASSERT_FALSE(ERR_TO_BOOL(function->execute(ctx.get())));
262 auto H = outputTensor->getHandle();
263 EXPECT_EQ(H.at(0), 3);
264 EXPECT_EQ(H.at(1), 4);
265}
266
267TEST_P(BackendCorrectnessTest, AvgPoolGradTest) {
268 CHECK_IF_ENABLED();
269 PseudoRNG PRNG;
270 Tensor inputs(ElemKind::FloatTy, {5, 7, 6, 3});
271 Tensor weights(ElemKind::FloatTy, {126, 72});
272 Tensor bias(ElemKind::FloatTy, {72});
273 Tensor selected(ElemKind::Int64ITy, {5, 1});
274 inputs.getHandle().initXavier(1, PRNG);
275 weights.getHandle().randomize(-0.3, 0.6, PRNG);
276 bias.getHandle().randomize(-0.2, 0.1, PRNG);
277 auto selectedH = selected.getHandle<int64_t>();
278 for (size_t i = 0; i < 5; i++) {
279 selectedH.raw(i) = PRNG.nextRandInt(0, 17);
280 }
281 std::array<dim_t, 4> S1{{5, 6, 4, 3}};
282 llvm::ArrayRef<dim_t> shape1(S1);
283 std::array<dim_t, 2> S2{{5, 18}};
284 llvm::ArrayRef<dim_t> shape2(S2);
285 Tensor out1;
286 Tensor out2;
287
288 trainAvgPoolNet(&inputs, &weights, &bias, &selected, shape1, shape2, &out1,
289 backendName_);
290 trainAvgPoolNet(&inputs, &weights, &bias, &selected, shape1, shape2, &out2,
291 "Interpreter");
292
293 EXPECT_TRUE(out1.isEqual(out2));
294}
295
296TEST_P(BackendCorrectnessTest, MaxPoolGradTest) {
297 CHECK_IF_ENABLED();
298 PseudoRNG PRNG;
299 Tensor inputs(ElemKind::FloatTy, {4, 8, 7, 2});
300 Tensor weights(ElemKind::FloatTy, {112, 84});
301 Tensor bias(ElemKind::FloatTy, {84});
302 Tensor selected(ElemKind::Int64ITy, {4, 1});
303 inputs.getHandle().initXavier(1, PRNG);
304 weights.getHandle().randomize(-0.1, 0.7, PRNG);
305 bias.getHandle().randomize(-0.3, 0.1, PRNG);
306 auto selectedH = selected.getHandle<int64_t>();
307 for (size_t i = 0; i < 4; i++) {
308 selectedH.raw(i) = PRNG.nextRandInt(0, 31);
309 }
310 std::array<dim_t, 4> S1{{4, 6, 7, 2}};
311 llvm::ArrayRef<dim_t> shape1(S1);
312 std::array<dim_t, 2> S2{{4, 32}};
313 llvm::ArrayRef<dim_t> shape2(S2);
314 Tensor out1;
315 Tensor out2;
316
317 trainMaxPoolNet(&inputs, &weights, &bias, &selected, shape1, shape2, &out1,
318 backendName_);
319 trainMaxPoolNet(&inputs, &weights, &bias, &selected, shape1, shape2, &out2,
320 "Interpreter");
321
322 EXPECT_TRUE(out1.isEqual(out2));
323}
324
325TEST_P(BackendCorrectnessTest, intLookupTableInt8) {
326 CHECK_IF_ENABLED();
327 PseudoRNG PRNG;
328 constexpr dim_t inputSize = 100;
329 Tensor inputs(ElemKind::Int8QTy, {inputSize}, 0.8, 4);
330 inputs.getHandle<int8_t>().randomize(-128, 127, PRNG);
331 Tensor out1, out2;
332
333 // Mapping i -> i.
334 std::vector<int8_t> initValues(256);
335 for (dim_t i = 0; i < 256; ++i) {
336 initValues[i] = i - 128;
337 }
338
339 inferIntLookupTableNetInt8(&inputs, &out1, initValues, backendName_);
340 inferIntLookupTableNetInt8(&inputs, &out2, initValues, "Interpreter");
341
342 EXPECT_TRUE(out1.isEqual(out2));
343}
344
345TEST_P(BackendCorrectnessTest, intLookupTableInt16) {
346 CHECK_IF_ENABLED();
347 PseudoRNG PRNG;
348 constexpr dim_t inputSize = 100;
349 Tensor inputs(ElemKind::Int16QTy, {inputSize}, 0.8, 4);
350 inputs.getHandle<int16_t>().randomize(-32768, 32767, PRNG);
351 Tensor out1, out2;
352
353 // Mapping i -> i.
354 std::vector<int16_t> initValues(65536);
355 for (dim_t i = 0; i < 65536; ++i) {
356 initValues[i] = i - 32768;
357 }
358
359 inferIntLookupTableNetInt16(&inputs, &out1, initValues, backendName_);
360 inferIntLookupTableNetInt16(&inputs, &out2, initValues, "Interpreter");
361
362 EXPECT_TRUE(out1.isEqual(out2));
363}
364
365TEST_P(BackendCorrectnessTest, smallConv) {
366 CHECK_IF_ENABLED();
367 Tensor input(ElemKind::FloatTy, {1, 3, 3, 32});
368 input.getHandle().clear(0.2);
369 Tensor out1;
370 Tensor out2;
371
372 inferSmallConv(&input, &out1, backendName_);
373 inferSmallConv(&input, &out2, "Interpreter");
374
375 EXPECT_TRUE(out1.isEqual(out2));
376}
377
378/// This test targets the DKKC8 optimization.
379TEST_P(BackendCorrectnessTest, groupConvTest) {
380 CHECK_IF_ENABLED();
381 std::array<dim_t, 4> S{{1, 2, 1, 128}};
382 llvm::ArrayRef<dim_t> shape(S);
383 Tensor out1(ElemKind::FloatTy, shape);
384 Tensor out2(ElemKind::FloatTy, shape);
385 inferGroupConv(&out1, backendName_);
386 inferGroupConv(&out2, "Interpreter");
387
388 EXPECT_TRUE(out1.isEqual(out2));
389}
390
391/// This test targets the DKKC8 optimization.
392TEST_P(BackendCorrectnessTest, nonSquarePaddingConvTest) {
393 CHECK_IF_ENABLED();
394 Tensor out1;
395 Tensor out2;
396
397 inferNonSquarePaddingConv(&out1, backendName_);
398 inferNonSquarePaddingConv(&out2, "Interpreter");
399
400 EXPECT_TRUE(out1.isEqual(out2));
401}
402
403/// This non-square kernel test targets the DKKC8 optimization.
404TEST_P(BackendCorrectnessTest, nonSquareKernelConvTest) {
405 CHECK_IF_ENABLED();
406
407 Tensor out1;
408 Tensor out2;
409
410 inferNonSquareKernelConv(&out1, backendName_);
411 inferNonSquareKernelConv(&out2, "Interpreter");
412
413 EXPECT_TRUE(out1.isEqual(out2));
414}
415
416/// This non-square stride test targets the DKKC8 optimization.
417TEST_P(BackendCorrectnessTest, nonSquareStrideConvTest) {
418 CHECK_IF_ENABLED();
419 Tensor out1;
420 Tensor out2;
421 inferNonSquareStrideConv(&out1, backendName_);
422 inferNonSquareStrideConv(&out2, "Interpreter");
423
424 EXPECT_TRUE(out1.isEqual(out2));
425}
426
427/// This test targets the DKKC8 opt correctionimization.
428TEST_P(BackendCorrectnessTest, convDKKC8Test) {
429 CHECK_IF_ENABLED();
430 Tensor out1;
431 Tensor out2;
432 inferConvDKKC8(&out1, backendName_);
433 inferConvDKKC8(&out2, "Interpreter");
434 EXPECT_TRUE(out1.isEqual(out2, 0.00013));
435}
436
437TEST_P(BackendCorrectnessTest, softmaxGradTest) {
438 CHECK_IF_ENABLED();
439 PseudoRNG PRNG;
440 std::array<dim_t, 2> S{{8, 23}};
441 llvm::ArrayRef<dim_t> shape(S);
442 Tensor inputs(ElemKind::FloatTy, shape);
443 Tensor weights(ElemKind::FloatTy, {23, 23});
444 Tensor bias(ElemKind::FloatTy, {23});
445 Tensor selected(ElemKind::Int64ITy, {8, 1});
446 inputs.getHandle().initXavier(1, PRNG);
447 weights.getHandle().randomize(0.0, 0.5, PRNG);
448 bias.getHandle().randomize(-0.2, 0.0, PRNG);
449 auto selectedH = selected.getHandle<int64_t>();
450 for (size_t i = 0; i < 8; i++) {
451 selectedH.raw(i) = PRNG.nextRandInt(0, 22);
452 }
453 Tensor out1;
454 Tensor out2;
455
456 trainSoftMaxNet(&inputs, &weights, &bias, &selected, &out1, backendName_);
457 trainSoftMaxNet(&inputs, &weights, &bias, &selected, &out2, "Interpreter");
458
459 EXPECT_TRUE(out1.isEqual(out2));
460}
461
462TEST_P(BackendCorrectnessTest, convOps) {
463 CHECK_IF_ENABLED();
464 PseudoRNG PRNG;
465 // Construct networks with a different convolution depth.
466 for (auto depth : {4, 64, 128}) {
467 Tensor inputs(ElemKind::FloatTy, {2, 32, 16, 16});
468 inputs.getHandle().initXavier(1, PRNG);
469 Tensor out1;
470 Tensor out2;
471
472 inferBasicConvNet(&inputs, &out1, backendName_, depth);
473 inferBasicConvNet(&inputs, &out2, "Interpreter", depth);
474
475 EXPECT_TRUE(out1.isEqual(out2));
476 }
477}
478
479TEST_P(BackendCorrectnessTest, basicFCNet) {
480 CHECK_IF_ENABLED();
481 compareAgainstInterpreter(GetParam(), createAndInitBasicFCNet,
482 ElemKind::FloatTy, ElemKind::FloatTy, 0.0004f,
483 parCloneCountOpt);
484}
485
486TEST_P(BackendCorrectnessTest, basicFCNetQuantized) {
487 CHECK_IF_ENABLED();
488 compareAgainstInterpreter(GetParam(), createAndInitBasicFCNet,
489 ElemKind::Int8QTy, ElemKind::Int8QTy, 0.f,
490 parCloneCountOpt);
491}
492
493TEST_P(BackendCorrectnessTest, complexNet1) {
494 CHECK_IF_ENABLED();
495 PseudoRNG PRNG;
496 std::array<dim_t, 4> S{{8, 7, 14, 11}};
497 llvm::ArrayRef<dim_t> shape(S);
498 Tensor inputs1(ElemKind::FloatTy, shape);
499 Tensor inputs2(ElemKind::FloatTy, {8, 4, 7, 9});
500 Tensor inputs3(ElemKind::FloatTy, shape);
501 Tensor inputs4(ElemKind::FloatTy, {8, 8, 7, 4});
502 inputs1.getHandle().initXavier(1, PRNG);
503 inputs2.getHandle().initXavier(1, PRNG);
504 inputs3.getHandle().initXavier(1, PRNG);
505 inputs4.getHandle().initXavier(1, PRNG);
506 Tensor out1;
507 Tensor out2;
508
509 inferComplexNet1(&inputs1, &inputs2, &inputs3, &inputs4, &out1, backendName_);
510 inferComplexNet1(&inputs1, &inputs2, &inputs3, &inputs4, &out2,
511 "Interpreter");
512
513 EXPECT_TRUE(out1.isEqual(out2));
514}
515
516TEST_P(BackendCorrectnessTest, tinyResnet) {
517 CHECK_IF_ENABLED();
518 PseudoRNG PRNG;
519 Tensor input(ElemKind::FloatTy, {1, 7, 7, 64});
520 input.getHandle().randomize(0, 1.0, PRNG);
521
522 std::vector<Tensor> weights;
523 using Dims = llvm::ArrayRef<dim_t>;
524 weights.emplace_back(ElemKind::FloatTy, Dims{256, 1, 1, 64});
525 weights.emplace_back(ElemKind::FloatTy, Dims{256});
526 weights.emplace_back(ElemKind::FloatTy, Dims{64, 1, 1, 256});
527 weights.emplace_back(ElemKind::FloatTy, Dims{64});
528 weights.emplace_back(ElemKind::FloatTy, Dims{64, 3, 3, 64});
529 weights.emplace_back(ElemKind::FloatTy, Dims{64});
530 weights.emplace_back(ElemKind::FloatTy, Dims{256, 1, 1, 64});
531 weights.emplace_back(ElemKind::FloatTy, Dims{256});
532 for (auto &T : weights) {
533 T.getHandle().initXavier(10.0, PRNG);
534 }
535
536 Tensor out1;
537 Tensor out2;
538 inferTinyResnet(&input, &out1, weights, "Interpreter");
539 inferTinyResnet(&input, &out2, weights, backendName_);
540
541 EXPECT_TRUE(out1.isEqual(out2, 0.001));
542}
543
544// Test MaxSplat transformation in CPU backend.
545TEST_P(BackendCorrectnessTest, maxSplatTest) {
546 CHECK_IF_ENABLED();
547 PseudoRNG PRNG;
548 Tensor input(ElemKind::Int8QTy, {5, 5}, 0.001, -10);
549 input.getHandle<int8_t>().randomize(-128, 127, PRNG);
550 Tensor out1, out2;
551
552 inferMaxSplat(&input, &out1, backendName_);
553 inferMaxSplat(&input, &out2, "Interpreter");
554
555 EXPECT_TRUE(out1.isEqual(out2));
556}
557
558void QuantizedConvReluFusionTest(quantization::Schema schema,
559 std::string backendName_, int expectedFusion) {
560 PseudoRNG PRNG;
561 Tensor inputs(ElemKind::Int8QTy, {1, 6, 6, 16}, 1, 2);
562 Tensor kernel(ElemKind::Int8QTy, {1, 3, 3, 16}, 1, 2);
563 Tensor bias(ElemKind::Int32QTy, {1}, 1, 2);
564 inputs.getHandle<int8_t>().randomize(0, 60, PRNG);
565 kernel.getHandle<int8_t>().randomize(-1, 1, PRNG);
566 bias.getHandle<int32_t>().randomize(0, 32768, PRNG);
567
568 TensorQuantizationParams quantParams =
569 chooseQuantizationParams({0.0, 6.0}, schema, ElemKind::Int8QTy);
570
571 Tensor out(ElemKind::Int8QTy, {1, 6, 6, 1}, quantParams.scale,
572 quantParams.offset);
573
574 int resFusion =
575 inferConvReluNet(&inputs, &kernel, &bias, &out, 3, 1, 1, backendName_);
576 // In asymmetric case, Conv and Relu are not fused.
577 EXPECT_EQ(resFusion, expectedFusion);
578}
579
580/// Symmetric Quantized Conv+Relu fusion testing for only OpenCL.
581TEST_P(BackendCorrectnessTest, SymmetricQuantizedConvReluFusionTest) {
582 CHECK_IF_ENABLED()
583 QuantizedConvReluFusionTest(quantization::Schema::Symmetric, backendName_,
584 FusedActivation::RELU);
585}
586
587/// Asymmetric Quantized Conv+Relu fusion testing for only OpenCL.
588TEST_P(BackendCorrectnessTest, AsymmetricQuantizedConvReluFusionTest) {
589 CHECK_IF_ENABLED()
590 QuantizedConvReluFusionTest(quantization::Schema::Asymmetric, backendName_,
591 FusedActivation::NONE);
592}
593
594INSTANTIATE_BACKEND_TEST(BackendCorrectnessTest);
595