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 | |
31 | using namespace glow; |
32 | using llvm::cast; |
33 | |
34 | class BackendCorrectnessTest : public ::testing::TestWithParam<std::string> { |
35 | protected: |
36 | std::string backendName_{GetParam()}; |
37 | }; |
38 | |
39 | TEST_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 | |
59 | TEST_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 | |
73 | TEST_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 | |
93 | TEST_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 | |
126 | TEST_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 | |
140 | TEST_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. |
171 | class MockCPUBackend : public BackendUsingGlowIR { |
172 | // The actual backend being wrapped. |
173 | std::unique_ptr<BackendUsingGlowIR> backend_; |
174 | |
175 | public: |
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 | |
199 | TEST_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 | |
267 | TEST_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 | |
296 | TEST_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 | |
325 | TEST_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 | |
345 | TEST_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 | |
365 | TEST_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. |
379 | TEST_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. |
392 | TEST_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. |
404 | TEST_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. |
417 | TEST_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. |
428 | TEST_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 | |
437 | TEST_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 | |
462 | TEST_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 | |
479 | TEST_P(BackendCorrectnessTest, basicFCNet) { |
480 | CHECK_IF_ENABLED(); |
481 | compareAgainstInterpreter(GetParam(), createAndInitBasicFCNet, |
482 | ElemKind::FloatTy, ElemKind::FloatTy, 0.0004f, |
483 | parCloneCountOpt); |
484 | } |
485 | |
486 | TEST_P(BackendCorrectnessTest, basicFCNetQuantized) { |
487 | CHECK_IF_ENABLED(); |
488 | compareAgainstInterpreter(GetParam(), createAndInitBasicFCNet, |
489 | ElemKind::Int8QTy, ElemKind::Int8QTy, 0.f, |
490 | parCloneCountOpt); |
491 | } |
492 | |
493 | TEST_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 | |
516 | TEST_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. |
545 | TEST_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 | |
558 | void 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. |
581 | TEST_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. |
588 | TEST_P(BackendCorrectnessTest, AsymmetricQuantizedConvReluFusionTest) { |
589 | CHECK_IF_ENABLED() |
590 | QuantizedConvReluFusionTest(quantization::Schema::Asymmetric, backendName_, |
591 | FusedActivation::NONE); |
592 | } |
593 | |
594 | INSTANTIATE_BACKEND_TEST(BackendCorrectnessTest); |
595 | |