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 "glow/Graph/Graph.h" |
18 | |
19 | #include "glow/IR/IR.h" |
20 | #include "glow/IR/IRBuilder.h" |
21 | #include "glow/IR/Instrs.h" |
22 | |
23 | #include "llvm/ADT/StringSet.h" |
24 | #include "llvm/Support/Casting.h" |
25 | |
26 | #include "gtest/gtest.h" |
27 | |
28 | #include <algorithm> |
29 | #include <cassert> |
30 | #include <cstddef> |
31 | #include <cstdint> |
32 | #include <iostream> |
33 | #include <string> |
34 | |
35 | using namespace glow; |
36 | using llvm::cast; |
37 | using llvm::dyn_cast; |
38 | using llvm::isa; |
39 | |
40 | TEST(IR, uniqueTypes) { |
41 | Module mod; |
42 | Type T1(ElemKind::FloatTy, {320, 200}); |
43 | Type T2(ElemKind::FloatTy, {320, 200}); |
44 | Type T3(ElemKind::FloatTy, {1, 2}); |
45 | |
46 | auto *u1 = mod.uniqueType(T1); |
47 | auto *u2 = mod.uniqueType(T2); |
48 | auto *u3 = mod.uniqueType(T3); |
49 | |
50 | EXPECT_EQ(u1, u2); |
51 | EXPECT_NE(u1, u3); |
52 | |
53 | for (int i = 0; i < 10; i++) { |
54 | EXPECT_EQ(u1, mod.uniqueType(T1)); |
55 | } |
56 | |
57 | // Check the uniqueing of quantized tensors. |
58 | Type T4(ElemKind::Int8QTy, {1, 2}, 0.4f, 2); |
59 | auto *t4 = mod.uniqueType(T4); |
60 | auto *u4 = mod.uniqueTypeWithNewShape(&T4, {2, 1}); |
61 | auto *q4 = mod.uniqueTypeWithNewShape(u4, {1, 2}); |
62 | |
63 | EXPECT_NE(t4, u4); |
64 | EXPECT_EQ(t4, q4); |
65 | } |
66 | |
67 | #define TEST_QUANT_TYPE(kind, type, type_name, scale, offset) \ |
68 | { \ |
69 | Type T(ElemKind::kind, {2, 3}, (scale), (offset)); \ |
70 | EXPECT_EQ(T.getElementType(), ElemKind::kind); \ |
71 | EXPECT_EQ(T.getScale(), (scale)); \ |
72 | EXPECT_EQ(T.getOffset(), (offset)); \ |
73 | EXPECT_TRUE(T.isQuantizedType()); \ |
74 | EXPECT_EQ(T.getElementSize(), sizeof(type)); \ |
75 | EXPECT_TRUE(T.isType<type>()); \ |
76 | auto range = T.getQuantizedValueRange(); \ |
77 | EXPECT_EQ(range.first, ((int64_t)type_name##_MIN - (offset)) * (scale)); \ |
78 | EXPECT_EQ(range.second, ((int64_t)type_name##_MAX - (offset)) * (scale)); \ |
79 | } |
80 | |
81 | TEST(IR, basicQuantizedTypes) { |
82 | // Quantized types |
83 | TEST_QUANT_TYPE(Int8QTy, int8_t, INT8, 0.3f, -45); |
84 | TEST_QUANT_TYPE(UInt8QTy, uint8_t, UINT8, 0.3f, -45); |
85 | TEST_QUANT_TYPE(Int16QTy, int16_t, INT16, 0.3f, -45); |
86 | TEST_QUANT_TYPE(Int32QTy, int32_t, INT32, 0.3f, -45); |
87 | |
88 | // Sanity check for non quantized types |
89 | Type TF(ElemKind::FloatTy, {2, 3}); |
90 | EXPECT_FALSE(TF.isQuantizedType()); |
91 | Type T64I(ElemKind::Int64ITy, {2, 3}); |
92 | EXPECT_FALSE(T64I.isQuantizedType()); |
93 | Type TU8I(ElemKind::UInt8ITy, {2, 3}); |
94 | EXPECT_FALSE(TU8I.isQuantizedType()); |
95 | } |
96 | |
97 | TEST(IR, basicUseList) { |
98 | Module mod; |
99 | Function *F = mod.createFunction("main" ); |
100 | IRFunction M(F); |
101 | { |
102 | IRBuilder builder(&M); |
103 | |
104 | auto *V1 = builder.createWeightVar(ElemKind::FloatTy, {320, 200}); |
105 | auto *V2 = builder.createWeightVar(ElemKind::FloatTy, {320, 200}); |
106 | |
107 | // Check that we can construct a new instruction. |
108 | auto *CC = builder.createCopyInst("C" , V1, V2); |
109 | InstructionNumbering IN(M); |
110 | CC->verifyUseList(IN); |
111 | |
112 | // Check the getOperand and setOperand functions. |
113 | EXPECT_EQ(CC->getDest(), V1); |
114 | CC->setOperand(0, V2); |
115 | EXPECT_EQ(CC->getDest(), V2); |
116 | CC->verifyUseList(IN); |
117 | } |
118 | |
119 | // Check that we can destroy the operands. |
120 | // ... |
121 | } |
122 | |
123 | static IRFunction *createTestIRFunction(Module &mod) { |
124 | using MK = WeightVar::MutabilityKind; |
125 | |
126 | Function *F = mod.createFunction("main" ); |
127 | IRFunction *M = new IRFunction(F); |
128 | auto T1 = mod.uniqueType(ElemKind::FloatTy, {1, 24, 24, 3}); |
129 | auto T2 = mod.uniqueType(ElemKind::FloatTy, {64}); |
130 | auto T4 = mod.uniqueType(ElemKind::Int64ITy, {1, 1}); |
131 | |
132 | { |
133 | IRBuilder builder(M); |
134 | |
135 | auto *I0 = builder.createWeightVar(T1, "I0" ); |
136 | auto *I1 = builder.createWeightVar(T1, "I1" ); |
137 | auto *I2 = builder.createWeightVar(ElemKind::FloatTy, {1, 3, 24, 24}, "I2" , |
138 | MK::Constant); |
139 | |
140 | auto *I3 = builder.createWeightVar(ElemKind::FloatTy, {1, 12, 12, 64}); |
141 | auto *I4 = builder.createWeightVar(ElemKind::FloatTy, {1, 12, 12, 3}); |
142 | auto *I6 = builder.createWeightVar(ElemKind::FloatTy, {2, 12, 12, 64}); |
143 | auto *I8 = builder.createWeightVar(ElemKind::FloatTy, {1, 24, 3, 24}, "I8" ); |
144 | auto *ComputationInfo = |
145 | builder.createWeightVar(ElemKind::FloatTy, {2}, "ComputationInfo" ); |
146 | |
147 | auto *argmax = builder.createWeightVar(ElemKind::Int64ITy, {1, 12, 12, 3}); |
148 | auto *B0 = builder.createWeightVar(T2, "B0" ); |
149 | auto *B1 = |
150 | builder.createWeightVar(ElemKind::FloatTy, {32}, "B1" , MK::Mutable); |
151 | auto *F0 = builder.createWeightVar(ElemKind::FloatTy, {64, 7, 7, 3}); |
152 | auto *F1 = builder.createWeightVar(ElemKind::FloatTy, {32, 1728}); |
153 | auto *E0 = builder.createWeightVar(T4, "E0" ); |
154 | |
155 | B0->setName("bias" ); |
156 | B1->setName("FC_bias" ); |
157 | F0->setName("filter" ); |
158 | F1->setName("FC_filter" ); |
159 | E0->setName("expected" ); |
160 | argmax->setName("argmax" ); |
161 | |
162 | builder.createCopyInst("" , I1, I0); |
163 | builder.createConvolutionInst("" , I3, I1, F0, B0, {7, 7}, {2, 2}, |
164 | {3, 3, 3, 3}, 1, {1, 1}, NHWC, |
165 | FusedActivation::NONE, {}); |
166 | builder.createMaxPoolInst("" , I4, I0, {7, 7}, {2, 2}, {3, 3, 3, 3}, NHWC); |
167 | builder.createSigmoidInst("" , I1, I0); |
168 | builder.createTanhInst("" , I1, I0); |
169 | builder.createSoftMaxInst("" , I1, I0); |
170 | builder.createTransposeInst("" , I8, I2, NHWC2NCHW); |
171 | builder.createTensorView(ElemKind::FloatTy, {1, 24, 3, 24}, I2, "I2_view" ); |
172 | builder.createInsertTensorInst("" , I6, I3, {0, 0, 0, 0}, 1, 0); |
173 | builder.createElementMulInst("" , I1, I0, I0); |
174 | builder.createDebugPrintInst("" , I0, "console" , "" ); |
175 | builder.createQuantizationProfileInst("" , I0, B0, ComputationInfo); |
176 | } |
177 | return M; |
178 | } |
179 | |
180 | TEST(IR, allInstrs) { |
181 | Module mod; |
182 | std::unique_ptr<IRFunction> M(createTestIRFunction(mod)); |
183 | M->verify(); |
184 | } |
185 | |
186 | /// Check the IR Functions cloning functionality. |
187 | TEST(IR, cloning) { |
188 | Module mod; |
189 | std::unique_ptr<IRFunction> M(createTestIRFunction(mod)); |
190 | std::unique_ptr<IRFunction> clonedM(M->clone(M->getName())); |
191 | auto dumpedM = M->toString(); |
192 | auto dumpedClonedM = clonedM->toString(); |
193 | EXPECT_EQ(dumpedM, dumpedClonedM); |
194 | } |
195 | |
196 | TEST(IR, casting) { |
197 | Module mod; |
198 | Function *F = mod.createFunction("main" ); |
199 | IRFunction M(F); |
200 | { |
201 | IRBuilder bb(&M); |
202 | |
203 | auto *input = bb.createWeightVar(ElemKind::FloatTy, {1, 224, 224, 3}); |
204 | auto *res = bb.createAllocActivationInst("sigmoid.res" , input->getType()); |
205 | auto *sig = bb.createSigmoidInst("sigmoid" , res, input); |
206 | auto *pool = |
207 | bb.createAvgPoolOp(sig->getDest(), {7, 7}, {2, 2}, {3, 3, 3, 3}, NHWC, |
208 | /* countIncludePads */ true); |
209 | |
210 | EXPECT_EQ(isa<AvgPoolInst>(pool), true); |
211 | EXPECT_EQ(isa<AvgPoolInst>(input), false); |
212 | EXPECT_EQ(isa<SigmoidInst>(sig), true); |
213 | EXPECT_EQ(isa<SigmoidInst>(pool), false); |
214 | |
215 | EXPECT_NE(dyn_cast<AvgPoolInst>(pool), nullptr); |
216 | EXPECT_EQ(dyn_cast<AvgPoolInst>(pool), pool); |
217 | |
218 | EXPECT_NE(dyn_cast<WeightVar>(input), nullptr); |
219 | EXPECT_EQ(dyn_cast<WeightVar>(input), input); |
220 | } |
221 | } |
222 | |
223 | TEST(IR, predicateIR) { |
224 | Module mod; |
225 | Function *F = mod.createFunction("predicated" ); |
226 | IRFunction M(F); |
227 | { |
228 | IRBuilder builder(&M); |
229 | auto *V1 = builder.createWeightVar(ElemKind::FloatTy, {320, 200}); |
230 | auto *V2 = builder.createWeightVar(ElemKind::FloatTy, {320, 200}); |
231 | auto *P = builder.createWeightVar(ElemKind::Int64ITy, {320}, "p1" ); |
232 | |
233 | // Check that we can construct a new instruction. |
234 | auto *CC = builder.createCopyInst("C" , V1, V2); |
235 | // Set the predicate. |
236 | CC->setPredicate(P); |
237 | InstructionNumbering IN(M); |
238 | CC->verifyUseList(IN); |
239 | M.verify(); |
240 | } |
241 | } |
242 | |
243 | /// Note that IRFunction validation uses asserts, so these tests only die when |
244 | /// asserts are turned on. |
245 | #ifndef NDEBUG |
246 | |
247 | /// Check that the verify call dies when verifying an IRFunction with a |
248 | /// non-memory/view Instruction with another non-memory/view Instruction as |
249 | /// an input operand. |
250 | TEST(IR, VerifyDiesOnInvalidInputOperand) { |
251 | Module mod; |
252 | Function *F = mod.createFunction("InvalidOperands" ); |
253 | IRFunction M(F); |
254 | { |
255 | IRBuilder builder(&M); |
256 | auto *LHS = builder.createWeightVar(ElemKind::FloatTy, {10, 10}); |
257 | auto *RHS = builder.createWeightVar(ElemKind::FloatTy, {10, 10}); |
258 | auto *O = builder.createWeightVar(ElemKind::FloatTy, {10, 10}); |
259 | auto *EAI = builder.createElementAddInst("Add" , O, LHS, RHS); |
260 | |
261 | // Invalid to use a non-memory/view Instruction as input operand. |
262 | builder.createElementAddInst("Add" , O, EAI, RHS); |
263 | |
264 | EXPECT_DEATH(M.verify(), "" ); |
265 | } |
266 | } |
267 | |
268 | /// Check that the verify call dies when verifying an IRFunction with a |
269 | /// non-memory/view Instruction with another non-memory/view Instruction as |
270 | /// an output operand. |
271 | TEST(IR, VerifyDiesOnInvalidOutputOperand) { |
272 | Module mod; |
273 | Function *F = mod.createFunction("InvalidOperands" ); |
274 | IRFunction M(F); |
275 | { |
276 | IRBuilder builder(&M); |
277 | auto *LHS = builder.createWeightVar(ElemKind::FloatTy, {10, 10}); |
278 | auto *RHS = builder.createWeightVar(ElemKind::FloatTy, {10, 10}); |
279 | auto *O = builder.createWeightVar(ElemKind::FloatTy, {10, 10}); |
280 | auto *EAI = builder.createElementAddInst("Add" , O, LHS, RHS); |
281 | |
282 | // Invalid to use a non-memory/view Instruction as output operand. |
283 | builder.createElementAddInst("Add" , EAI, LHS, RHS); |
284 | |
285 | EXPECT_DEATH(M.verify(), "" ); |
286 | } |
287 | } |
288 | |
289 | #endif /* NDEBUG */ |
290 | |
291 | /// Verify that names of Instructions and WeightVars are uniqued when given the |
292 | /// same name. |
293 | TEST(IR, InstUniqueNames) { |
294 | Module mod; |
295 | Function *F = mod.createFunction("main" ); |
296 | IRFunction M(F); |
297 | { |
298 | const std::string name = "name" ; |
299 | |
300 | // Add all of the names of the Instructions/WeightVars created to this set |
301 | // to verify they are unique. |
302 | llvm::StringSet<> nameSet; |
303 | |
304 | IRBuilder builder(&M); |
305 | WeightVar *V1 = |
306 | builder.createWeightVar(ElemKind::FloatTy, {1, 4, 4, 1}, name); |
307 | auto it = nameSet.insert(V1->getName()); |
308 | EXPECT_TRUE(it.second); |
309 | |
310 | WeightVar *V2 = builder.createWeightVar(ElemKind::FloatTy, {4}, name); |
311 | it = nameSet.insert(V2->getName()); |
312 | EXPECT_TRUE(it.second); |
313 | |
314 | MaxPoolWithArgmaxInst *MP1 = builder.createMaxPoolWithArgmaxOp( |
315 | name, V1, {2, 2}, {1, 1}, {0, 2, 1, 3}, NHWC, ElemKind::Int64ITy); |
316 | it = nameSet.insert(MP1->getName()); |
317 | EXPECT_TRUE(it.second); |
318 | |
319 | // IRBuilder::createMaxPoolWithArgmaxOp() creates alloc activation insts |
320 | // internally, so we dealloc them here to keep the instruction list |
321 | // well-formed. |
322 | DeallocActivationInst *DAI1 = |
323 | builder.createDeallocActivationInst(name, MP1->getArgmax()); |
324 | it = nameSet.insert(DAI1->getName()); |
325 | EXPECT_TRUE(it.second); |
326 | |
327 | DeallocActivationInst *DAI2 = |
328 | builder.createDeallocActivationInst(name, MP1->getDest()); |
329 | it = nameSet.insert(DAI2->getName()); |
330 | EXPECT_TRUE(it.second); |
331 | |
332 | // IRBuilder::createTopKOp() creates alloc activation insts internally, so |
333 | // we dealloc them here to keep the instruction list well-formed. |
334 | TopKInst *TK = builder.createTopKOp(name, V2, 2, ElemKind::Int64ITy); |
335 | it = nameSet.insert(TK->getName()); |
336 | EXPECT_TRUE(it.second); |
337 | |
338 | DeallocActivationInst *DAI3 = |
339 | builder.createDeallocActivationInst(name, TK->getScratch()); |
340 | it = nameSet.insert(DAI3->getName()); |
341 | EXPECT_TRUE(it.second); |
342 | |
343 | DeallocActivationInst *DAI4 = |
344 | builder.createDeallocActivationInst(name, TK->getValues()); |
345 | it = nameSet.insert(DAI4->getName()); |
346 | EXPECT_TRUE(it.second); |
347 | |
348 | DeallocActivationInst *DAI5 = |
349 | builder.createDeallocActivationInst(name, TK->getIndices()); |
350 | it = nameSet.insert(DAI5->getName()); |
351 | EXPECT_TRUE(it.second); |
352 | |
353 | M.verify(); |
354 | } |
355 | } |
356 | |
357 | TEST(IR, getOperandName) { |
358 | Module mod; |
359 | Function *F = mod.createFunction("main" ); |
360 | IRFunction M(F); |
361 | { |
362 | IRBuilder bb(&M); |
363 | |
364 | auto *input = bb.createWeightVar(ElemKind::FloatTy, {1, 224, 224, 3}); |
365 | auto *res = bb.createAllocActivationInst("sigmoid.res" , input->getType()); |
366 | auto *sig = bb.createSigmoidInst("sigmoid" , res, input); |
367 | auto *pool = |
368 | bb.createAvgPoolOp(sig->getDest(), {7, 7}, {2, 2}, {3, 3, 3, 3}, NHWC, |
369 | /* countIncludePads */ true); |
370 | |
371 | EXPECT_EQ(pool->getNumOperands(), 2); |
372 | EXPECT_EQ(pool->getOperandName(0), "Dest" ); |
373 | EXPECT_EQ(pool->getOperandName(1), "Src" ); |
374 | } |
375 | } |
376 | |
377 | /// Check that Scratch is allocated properly for instructions. |
378 | TEST(IR, scratchAllocation) { |
379 | Module mod; |
380 | Function *F = mod.createFunction("main" ); |
381 | IRFunction M(F); |
382 | { |
383 | IRBuilder bb(&M); |
384 | auto *input = bb.createWeightVar(ElemKind::FloatTy, {10}); |
385 | TopKInst *topk = bb.createTopKOp("topk" , input, 3, ElemKind::Int64ITy); |
386 | // Verify scratch is allocated and has correct size. |
387 | auto *scratch = topk->getScratch(); |
388 | EXPECT_TRUE(isa<AllocActivationInst>(scratch)); |
389 | EXPECT_EQ(scratch->getType()->size(), topk->getScratchSize()); |
390 | } |
391 | } |
392 | |