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/IR/IR.h"
22 #include "glow/Optimizer/GraphOptimizer/GraphOptimizer.h"
23 #include "glow/Quantization/Base/Base.h"
24 #include "glow/Quantization/Base/Calibration.h"
25 #include "glow/Quantization/Base/Profile.h"
26 #include "glow/Quantization/Quantization.h"
27 #include "glow/Quantization/Serialization.h"
28 
29 #include "gtest/gtest.h"
30 
31 #include "llvm/ADT/SmallVector.h"
32 #include "llvm/Support/FileSystem.h"
33 
34 namespace glow {
35 
36 using llvm::cast;
37 
38 class Quantization : public ::testing::TestWithParam<std::string> {};
39 
40 class Operator
41     : public ::testing::TestWithParam<::std::tuple<std::string, std::string>> {
42 protected:
43   ExecutionEngine profileEE{};
44   ExecutionEngine backendSpecificEE{};
45 
SetUp()46   void SetUp() override {
47     std::string backend1;
48     std::string backend2;
49     std::tie(backend1, backend2) = GetParam();
50     profileEE.setBackendName(backend1);
51     backendSpecificEE.setBackendName(backend2);
52   }
53 };
54 
55 class InterpAndCPU : public Operator {};
56 
operator ==(const std::vector<float> & lhs,const std::vector<float> & rhs)57 bool operator==(const std::vector<float> &lhs, const std::vector<float> &rhs) {
58   return std::equal(lhs.begin(), lhs.end(), rhs.begin());
59 }
60 
operator ==(const NodeProfilingInfo & lhs,const NodeProfilingInfo & rhs)61 bool operator==(const NodeProfilingInfo &lhs, const NodeProfilingInfo &rhs) {
62   return lhs.min() == rhs.min() && lhs.max() == rhs.max() &&
63          lhs.nodeOutputName_ == rhs.nodeOutputName_ &&
64          lhs.histogram() == rhs.histogram();
65 }
66 
operator ==(const NodeQuantizationInfo & lhs,const NodeQuantizationInfo & rhs)67 bool operator==(const NodeQuantizationInfo &lhs,
68                 const NodeQuantizationInfo &rhs) {
69   return lhs.scale() == rhs.scale() && lhs.offset() == rhs.offset() &&
70          lhs.nodeOutputName_ == rhs.nodeOutputName_;
71 }
72 
73 /// This is a mock backend which extended support of quantized operators.
74 class MockQuantBackend : public Backend {
75   // The actual backend being wrapped.
76   std::unique_ptr<Backend> backend_;
77 
78 public:
MockQuantBackend()79   MockQuantBackend() { backend_.reset(createBackend("Interpreter")); }
80 
getBackendName() const81   std::string getBackendName() const override { return "Interpreter"; }
82 
83   Expected<std::unique_ptr<CompiledFunction>>
compile(Function * F,const BackendOptions & opts) const84   compile(Function *F, const BackendOptions &opts) const override {
85     return backend_->compile(F, opts);
86   }
87 
88   runtime::DeviceManager *
createDeviceManager(const runtime::DeviceConfig & deviceConfig)89   createDeviceManager(const runtime::DeviceConfig &deviceConfig) override {
90     return nullptr;
91   }
92 
isOpSupported(const NodeInfo & NI) const93   bool isOpSupported(const NodeInfo &NI) const override {
94     if (NI.getKind() == Kinded::Kind::SoftMaxNodeKind ||
95         NI.getKind() == Kinded::Kind::LocalResponseNormalizationNodeKind ||
96         NI.getKind() == Kinded::Kind::SaveNodeKind ||
97         NI.getKind() == Kinded::Kind::ReluNodeKind ||
98         NI.getKind() == Kinded::Kind::SelectNodeKind ||
99         NI.getKind() == Kinded::Kind::LogNodeKind ||
100         NI.getKind() == Kinded::Kind::SigmoidNodeKind ||
101         NI.getKind() == Kinded::Kind::TanhNodeKind) {
102       return true;
103     }
104     return backend_->isOpSupported(NI);
105   }
106 };
107 
108 /// Simple tests to verify the histogram rescale.
TEST(Quantization,rescaleHistogramTest)109 TEST(Quantization, rescaleHistogramTest) {
110   EXPECT_EQ(quantization::rescaleHistogram({}, 0.0f, 1.0f, 0.0f, 2.0).size(),
111             0);
112   EXPECT_EQ(
113       quantization::rescaleHistogram({1, 2, 3, 4}, 0.0f, 1.0f, -1.0f, 1.0),
114       std::vector<float>({0, 0, 3, 7}));
115   EXPECT_EQ(
116       quantization::rescaleHistogram({2, 4, 6, 8}, -1.0f, 1.0f, 0.0f, 1.0),
117       std::vector<float>({3, 3, 4, 4}));
118 }
119 
120 /// Simple tests to verify the KL optimization.
TEST(Quantization,optimizeKLTest)121 TEST(Quantization, optimizeKLTest) {
122   // Test that an all-zero histogram does not raise exceptions.
123   std::vector<float> histAllZero(1000, 0);
124   quantization::FloatRange rangeAllZero =
125       quantization::optimizeKL(histAllZero, 0.f, 1.0f, 255);
126   EXPECT_EQ(rangeAllZero.first, 0.f);
127   EXPECT_EQ(rangeAllZero.second, 1.0f);
128 
129   // Test that an empty histogram does not raise exceptions.
130   std::vector<float> histEmpty;
131   quantization::FloatRange rangeEmpty =
132       quantization::optimizeKL(histEmpty, 0.f, 1.0f, 255);
133   EXPECT_EQ(rangeEmpty.first, 0.f);
134   EXPECT_EQ(rangeEmpty.second, 1.0f);
135 }
136 
testProfilingInfosSerialization(std::vector<NodeProfilingInfo> & expected)137 void testProfilingInfosSerialization(std::vector<NodeProfilingInfo> &expected) {
138   llvm::SmallVector<char, 10> resultPath;
139   llvm::sys::fs::createTemporaryFile("prefix", "suffix", resultPath);
140   std::string filePath(resultPath.begin(), resultPath.end());
141   llvm::hash_code hash = 13;
142   serializeProfilingInfosToYaml(filePath, hash, expected);
143   std::vector<NodeProfilingInfo> deserialized;
144   llvm::hash_code hashDeserialized;
145   deserializeProfilingInfosFromYaml(filePath, hashDeserialized, deserialized);
146   llvm::sys::fs::remove(filePath);
147   EXPECT_EQ(static_cast<size_t>(hash), static_cast<size_t>(hashDeserialized));
148   EXPECT_EQ(expected, deserialized);
149 }
150 
TEST(Quantization,ProfilingSerialize)151 TEST(Quantization, ProfilingSerialize) {
152   std::vector<float> histEmpty;
153   std::vector<float> hist = {0, 1, 2, 3, 4};
154   std::vector<NodeProfilingInfo> expected{{"first", {1.0, 10.0, histEmpty}},
155                                           {"second", {-1.0, 3.0, hist}},
156                                           {"third", {-10.0, 30.0, hist}},
157                                           {"fourth", {0.1, 10.0, hist}},
158                                           {"fifth", {0.123, 30.0, hist}}};
159   testProfilingInfosSerialization(expected);
160 }
161 
TEST(Quantization,ProfilingSerializePower2Range)162 TEST(Quantization, ProfilingSerializePower2Range) {
163   std::vector<NodeProfilingInfo> expected{
164       {"pwr_0", {1.0000000000f, 1.0f}},   {"pwr_1", {0.5000000000f, 2.0f}},
165       {"pwr_2", {0.2500000000f, 4.0f}},   {"pwr_3", {0.1250000000f, 8.0f}},
166       {"pwr_4", {0.0625000000f, 16.0f}},  {"pwr_5", {0.0312500000f, 32.0f}},
167       {"pwr_6", {0.0156250000f, 64.0f}},  {"pwr_7", {0.0078125000f, 128.0f}},
168       {"pwr_8", {0.0039062500f, 256.0f}}, {"pwr_9", {0.0019531250f, 512.0f}}};
169   testProfilingInfosSerialization(expected);
170 }
171 
172 #if LLVM_VERSION_MAJOR < 8
TEST(Quantization,ProfilingSerializeEmpty)173 TEST(Quantization, ProfilingSerializeEmpty) {
174   std::vector<NodeProfilingInfo> expected;
175   testProfilingInfosSerialization(expected);
176 }
177 #endif
178 
TEST(Quantization,tensorAverageValue)179 TEST(Quantization, tensorAverageValue) {
180   {
181     float min = -10.0;
182     float max = 10.0;
183     std::vector<float> hist = {64, 64};
184     TensorProfilingParams profParams(min, max, hist);
185     float avgVal = quantization::getTensorAverageValue(profParams);
186     EXPECT_FLOAT_EQ(avgVal, 0.0);
187   }
188   {
189     float min = -10.0;
190     float max = 10.0;
191     std::vector<float> hist = {0, 64};
192     TensorProfilingParams profParams(min, max, hist);
193     float avgVal = quantization::getTensorAverageValue(profParams);
194     EXPECT_FLOAT_EQ(avgVal, 5.0);
195   }
196   {
197     float min = 0.0;
198     float max = 10.0;
199     std::vector<float> hist = {64, 0};
200     TensorProfilingParams profParams(min, max, hist);
201     float avgVal = quantization::getTensorAverageValue(profParams);
202     EXPECT_FLOAT_EQ(avgVal, 2.5);
203   }
204 }
205 
clip(From in)206 template <typename From, typename To> static To clip(From in) {
207   static_assert(sizeof(From) >= sizeof(To),
208                 "Clip should reduce the variable size");
209   auto mx = std::numeric_limits<To>::max();
210   auto mn = std::numeric_limits<To>::min();
211   return std::max<From>(mn, std::min<From>(mx, in));
212 }
213 
TEST(Quantization,quantScaleOffset)214 TEST(Quantization, quantScaleOffset) {
215   // Test different scale values from 1<<-23 to 1>>1.
216   float scales[] = {
217       0.0000001596f, 0.00000025f, 0.000000995f, 0.0000035f, 0.00000952f,
218       0.00000113f,   0.000721f,   0.0000721f,   0.0000172f, 0.0000951f,
219       0.0000721f,    0.0000341f,  0.0000222f,   0.0000172f, 0.000752f,
220       0.000371f,     0.000321f,   0.000223f,    0.000112f,  0.00852f,
221       0.00671f,      0.00592f,    0.00200f,     0.00107f,   0.0931f,
222       0.0721f,       0.031f,      0.014f,       0.0132f,    0.712f,
223       0.613f,        0.412f,      0.223f,       0.134f,     1.0f,
224       1.13f,         1.612f,      1.523f,       2.0f};
225 
226   // Try all scale factors:
227   for (float scale : scales) {
228     // Try all legal integers within the range:
229     for (int8_t input = -128; input < 127; input++) {
230       int32_t sum32num = round(input / scale);
231 
232       auto TR = quantization::quantizeScaleOffset32To8(scale, 0);
233       int32_t computed = TR.transform(sum32num);
234 
235       EXPECT_NEAR(input, computed, 1);
236     }
237   }
238 }
239 
TEST(Quantization,quantScaleOffsetPower2Scale)240 TEST(Quantization, quantScaleOffsetPower2Scale) {
241   // Test different power of 2 scale values (from 2^-10 to 2^1).
242   float scales[] = {0.0009765625f, 0.0019531250f, 0.0039062500f, 0.0078125000f,
243                     0.0156250000f, 0.0312500000f, 0.0625000000f, 0.1250000000f,
244                     0.2500000000f, 0.5000000000f, 1.0000000000f, 2.0000000000f};
245 
246   // Try all scale factors:
247   for (float scale : scales) {
248     // Try all legal integers within the range:
249     for (int8_t input = -128; input < 127; input++) {
250       int32_t sum32num = round(input / scale);
251       auto TR = quantization::quantizeScaleOffset32To8(scale, 0);
252       EXPECT_EQ(quantization::isFloatPowerOf2(scale), true);
253       EXPECT_EQ(TR.pre, 0);
254       int exp = quantization::getFloat2Exp(scale);
255       if (exp > 0) {
256         EXPECT_EQ(TR.scale, (int)scale);
257         EXPECT_EQ(TR.post, 0);
258       } else {
259         EXPECT_EQ(TR.scale, 1);
260         EXPECT_EQ(TR.post, -exp);
261       }
262       int32_t computed = TR.transform(sum32num);
263       EXPECT_NEAR(input, computed, 1);
264     }
265   }
266 }
267 
268 template <class qtype>
quantizeTensorTest(ElemKind qTy,quantization::Schema schema)269 void quantizeTensorTest(ElemKind qTy, quantization::Schema schema) {
270   // Map float [0.0; 6.0] to a quantized type using its entire value range.
271   TensorQuantizationParams quantParams =
272       chooseQuantizationParams({0.0, 6.0}, schema, qTy);
273 
274   // Create an FP32 tensor with 6 elements and initialize it with numbers from 0
275   // to 5.
276   Tensor inputFP32(ElemKind::FloatTy, {6});
277   Handle<float> THFP32 = inputFP32.getHandle<float>();
278   for (unsigned i = 0; i < 6; ++i) {
279     THFP32.at({i}) = i * 1.0f;
280   }
281 
282   // Quantize the tensor.
283   auto quantizedFP32 =
284       quantization::quantizeTensor(inputFP32, quantParams, qTy);
285   // Check that the dequantized result is close to the original values before
286   // the quantization.
287   Handle<qtype> THquantizedFP32 = quantizedFP32.getHandle<qtype>();
288   for (unsigned i = 0; i < 6; ++i) {
289     EXPECT_NEAR(THFP32.at({i}),
290                 quantization::dequantize(THquantizedFP32.at({i}), quantParams),
291                 0.05f);
292   }
293 
294   // Create an FP16 tensor with 6 elements and initialize it with numbers from 0
295   // to 5.
296   Tensor inputFP16(ElemKind::Float16Ty, {6});
297   Handle<float16> THFP16 = inputFP16.getHandle<float16>();
298   for (unsigned i = 0; i < 6; ++i) {
299     THFP16.at({i}) = i * 1.0f;
300   }
301 
302   // Quantize the tensor.
303   auto quantizedFP16 =
304       quantization::quantizeTensor(inputFP16, quantParams, qTy);
305   // Check that the dequantized result is close to the original values before
306   // the quantization.
307   Handle<qtype> THquantizedFP16 = quantizedFP16.getHandle<qtype>();
308   for (unsigned i = 0; i < 6; ++i) {
309     EXPECT_NEAR(THFP16.at({i}),
310                 quantization::dequantize(THquantizedFP16.at({i}), quantParams),
311                 0.05f);
312   }
313 }
314 
TEST(Quantization,quantizeTensorAsymmetricInt8)315 TEST(Quantization, quantizeTensorAsymmetricInt8) {
316   quantizeTensorTest<int8_t>(ElemKind::Int8QTy,
317                              quantization::Schema::Asymmetric);
318 }
TEST(Quantization,quantizeTensorAsymmetricInt16)319 TEST(Quantization, quantizeTensorAsymmetricInt16) {
320   quantizeTensorTest<int16_t>(ElemKind::Int16QTy,
321                               quantization::Schema::Asymmetric);
322 }
TEST(Quantization,quantizeTensorAsymmetricInt32)323 TEST(Quantization, quantizeTensorAsymmetricInt32) {
324   quantizeTensorTest<int32_t>(ElemKind::Int32QTy,
325                               quantization::Schema::Asymmetric);
326 }
TEST(Quantization,quantizeTensorSymmetricInt8)327 TEST(Quantization, quantizeTensorSymmetricInt8) {
328   quantizeTensorTest<int8_t>(ElemKind::Int8QTy,
329                              quantization::Schema::Symmetric);
330 }
TEST(Quantization,quantizeTensorSymmetricInt16)331 TEST(Quantization, quantizeTensorSymmetricInt16) {
332   quantizeTensorTest<int16_t>(ElemKind::Int16QTy,
333                               quantization::Schema::Symmetric);
334 }
TEST(Quantization,quantizeTensorSymmetricInt32)335 TEST(Quantization, quantizeTensorSymmetricInt32) {
336   quantizeTensorTest<int32_t>(ElemKind::Int32QTy,
337                               quantization::Schema::Symmetric);
338 }
TEST(Quantization,quantizeTensorSymmetricUInt8)339 TEST(Quantization, quantizeTensorSymmetricUInt8) {
340   quantizeTensorTest<int8_t>(ElemKind::Int8QTy,
341                              quantization::Schema::SymmetricWithUnsigned);
342 }
TEST(Quantization,quantizeTensorSymmetricUInt16)343 TEST(Quantization, quantizeTensorSymmetricUInt16) {
344   quantizeTensorTest<int16_t>(ElemKind::Int16QTy,
345                               quantization::Schema::SymmetricWithUnsigned);
346 }
TEST(Quantization,quantizeTensorSymmetricUInt32)347 TEST(Quantization, quantizeTensorSymmetricUInt32) {
348   quantizeTensorTest<int32_t>(ElemKind::Int32QTy,
349                               quantization::Schema::SymmetricWithUnsigned);
350 }
TEST(Quantization,quantizeTensorSymmetricPwr2Int8)351 TEST(Quantization, quantizeTensorSymmetricPwr2Int8) {
352   quantizeTensorTest<int8_t>(ElemKind::Int8QTy,
353                              quantization::Schema::SymmetricWithPower2Scale);
354 }
TEST(Quantization,quantizeTensorSymmetricPwr2Int16)355 TEST(Quantization, quantizeTensorSymmetricPwr2Int16) {
356   quantizeTensorTest<int16_t>(ElemKind::Int16QTy,
357                               quantization::Schema::SymmetricWithPower2Scale);
358 }
TEST(Quantization,quantizeTensorSymmetricPwr2Int32)359 TEST(Quantization, quantizeTensorSymmetricPwr2Int32) {
360   quantizeTensorTest<int32_t>(ElemKind::Int32QTy,
361                               quantization::Schema::SymmetricWithPower2Scale);
362 }
363 
364 /// Test 4-bit fused rowwise quantization.
TEST(Quantization,fused4BitsRowwiseQuantizeTensor)365 TEST(Quantization, fused4BitsRowwiseQuantizeTensor) {
366   // Create an FP32 tensor with 12 elements and initialize it
367   // with numbers from the following test inputs here.
368   // 1. Input that contains at least one +ve, one -ve and zero.
369   // 2. Input that contains at least one +ve and zero.
370   // 3. Input that contains at least one -ve and zero.
371   // 4. Input that contains at least only (+ve) numbers.
372   // 5. Input that contains at least only (-ve) numbers.
373   // 'deltas' is used to create the above 5 test cases hermetically.
374   auto deltas = {-3, 0, 3, -7, 7};
375   for (const auto &delta : deltas) {
376     Tensor inputFP32(ElemKind::FloatTy, {2, 6});
377     Tensor dequantized(ElemKind::FloatTy, {2, 6});
378     Tensor quantized(ElemKind::UInt4FusedFP16QTy, {2, 7}, /* dummy scale */ 1.0,
379                      /* dummy offset */ 0);
380     Handle<float> inputH = inputFP32.getHandle<float>();
381     for (dim_t i = 0; i < 2; i++) {
382       for (dim_t j = 0; j < 6; j++) {
383         inputH.at({i, j}) = (i + j) * 1.0f + delta;
384       }
385     }
386 
387     quantization::tensorFusedRowwiseQuantization<float16_t>(inputFP32,
388                                                             quantized);
389     dequantized =
390         quantization::tensor4BitsFusedRowwiseDequantization(quantized);
391 
392     Handle<float> dequantizedH = dequantized.getHandle<float>();
393     for (dim_t i = 0; i < 2; i++) {
394       for (dim_t j = 0; j < 6; j++) {
395         EXPECT_NEAR(inputH.at({i, j}), dequantizedH.at({i, j}), 0.02f);
396       }
397     }
398   }
399 }
400 
401 /// When quantizing a scalar the quantization should not lose precision: the
402 /// quantize->dequantize pair applied to a float scalar should preserve the
403 /// value (up to the precision lost by dividing/multiplying with the scale).
quantizeScalarTest(float val,ElemKind qTy,quantization::Schema schema)404 void quantizeScalarTest(float val, ElemKind qTy, quantization::Schema schema) {
405   ExecutionEngine EE{};
406   auto &mod = EE.getModule();
407   Function *F = mod.createFunction("main");
408   PlaceholderBindings bindings;
409 
410   // Choose quantization parameters
411   auto TQP = quantization::chooseQuantizationParams({val, val}, schema, qTy);
412 
413   // Create quantize/dequantize network for a single float value
414   auto *input = mod.createPlaceholder(ElemKind::FloatTy, {1}, "val", false);
415   auto inputQTy = mod.uniqueType(qTy, {1}, TQP.scale, TQP.offset);
416   QuantizeNode *quant = F->createQuantize("quant", input, inputQTy);
417   DequantizeNode *dequant =
418       F->createDequantize("dequant", quant, ElemKind::FloatTy);
419   SaveNode *save = F->createSave("save", dequant);
420 
421   // Allocate placeholders, set input, run, get output
422   auto inpH = bindings.allocate(input)->getHandle();
423   auto outH = bindings.allocate(save->getPlaceholder())->getHandle();
424   inpH.at({0}) = val;
425   EE.compile(CompilationMode::Infer);
426   EE.run(bindings);
427   float outVal = outH.raw(0);
428   EXPECT_NEAR(val, outVal, 0.0000000001);
429 }
430 
TEST(Quantization,quantizeScalarTestInt8)431 TEST(Quantization, quantizeScalarTestInt8) {
432   quantizeScalarTest(0.0, ElemKind::Int8QTy, quantization::Schema::Asymmetric);
433   quantizeScalarTest(0.0, ElemKind::Int8QTy, quantization::Schema::Symmetric);
434   quantizeScalarTest(0.0, ElemKind::Int8QTy,
435                      quantization::Schema::SymmetricWithUnsigned);
436   quantizeScalarTest(1.3, ElemKind::Int8QTy, quantization::Schema::Asymmetric);
437   quantizeScalarTest(1.3, ElemKind::Int8QTy, quantization::Schema::Symmetric);
438   quantizeScalarTest(1.3, ElemKind::Int8QTy,
439                      quantization::Schema::SymmetricWithUnsigned);
440   quantizeScalarTest(-1.3, ElemKind::Int8QTy, quantization::Schema::Asymmetric);
441   quantizeScalarTest(-1.3, ElemKind::Int8QTy, quantization::Schema::Symmetric);
442   quantizeScalarTest(-1.3, ElemKind::Int8QTy,
443                      quantization::Schema::SymmetricWithUnsigned);
444 }
445 
TEST(Quantization,quantizeScalarTestInt16)446 TEST(Quantization, quantizeScalarTestInt16) {
447   quantizeScalarTest(0.0, ElemKind::Int16QTy, quantization::Schema::Asymmetric);
448   quantizeScalarTest(0.0, ElemKind::Int16QTy, quantization::Schema::Symmetric);
449   quantizeScalarTest(0.0, ElemKind::Int16QTy,
450                      quantization::Schema::SymmetricWithUnsigned);
451   quantizeScalarTest(1.3, ElemKind::Int16QTy, quantization::Schema::Asymmetric);
452   quantizeScalarTest(1.3, ElemKind::Int16QTy, quantization::Schema::Symmetric);
453   quantizeScalarTest(1.3, ElemKind::Int16QTy,
454                      quantization::Schema::SymmetricWithUnsigned);
455   quantizeScalarTest(-1.3, ElemKind::Int16QTy,
456                      quantization::Schema::Asymmetric);
457   quantizeScalarTest(-1.3, ElemKind::Int16QTy, quantization::Schema::Symmetric);
458   quantizeScalarTest(-1.3, ElemKind::Int16QTy,
459                      quantization::Schema::SymmetricWithUnsigned);
460 }
461 
TEST(Quantization,quantizeScalarTestInt32)462 TEST(Quantization, quantizeScalarTestInt32) {
463   quantizeScalarTest(0.0, ElemKind::Int32QTy, quantization::Schema::Asymmetric);
464   quantizeScalarTest(0.0, ElemKind::Int32QTy, quantization::Schema::Symmetric);
465   quantizeScalarTest(0.0, ElemKind::Int32QTy,
466                      quantization::Schema::SymmetricWithUnsigned);
467   quantizeScalarTest(1.3, ElemKind::Int32QTy, quantization::Schema::Asymmetric);
468   quantizeScalarTest(1.3, ElemKind::Int32QTy, quantization::Schema::Symmetric);
469   quantizeScalarTest(1.3, ElemKind::Int32QTy,
470                      quantization::Schema::SymmetricWithUnsigned);
471   quantizeScalarTest(-1.3, ElemKind::Int32QTy,
472                      quantization::Schema::Asymmetric);
473   quantizeScalarTest(-1.3, ElemKind::Int32QTy, quantization::Schema::Symmetric);
474   quantizeScalarTest(-1.3, ElemKind::Int32QTy,
475                      quantization::Schema::SymmetricWithUnsigned);
476 }
477 
478 /// Check corner case when bias is quantized as int32 with unconstrained
479 /// scale and offset parameters and used within a subtraction bias - biasOffset
480 /// which is expected to be within int32 limits.
quantizeBiasInt32CornerCaseTest(float val)481 static void quantizeBiasInt32CornerCaseTest(float val) {
482   // Choose bias quantization parameters
483   float biasF = val;
484   auto biasTQP = quantization::chooseQuantizationParams(
485       {biasF, biasF}, quantization::Schema::Asymmetric, ElemKind::Int32QTy);
486 
487   // Quantize the tensor.
488   Tensor biasTF(ElemKind::FloatTy, {1});
489   biasTF.getHandle<float>().at({0}) = biasF;
490   auto biasTQ =
491       quantization::quantizeTensor(biasTF, biasTQP, ElemKind::Int32QTy);
492   int32_t biasQ = biasTQ.getHandle<int32_t>().at({0});
493   int32_t biasOffset = biasTQP.offset;
494 
495   // Compute difference and check against int32 limits.
496   int64_t diff = ((int64_t)biasQ) - ((int64_t)biasOffset);
497   EXPECT_TRUE(std::numeric_limits<int32_t>::min() <= diff);
498   EXPECT_TRUE(diff <= std::numeric_limits<int32_t>::max());
499 }
500 
TEST(Quantization,quantizeBiasInt32CornerCaseTests)501 TEST(Quantization, quantizeBiasInt32CornerCaseTests) {
502   quantizeBiasInt32CornerCaseTest(0.0);
503   quantizeBiasInt32CornerCaseTest(0.3);
504   quantizeBiasInt32CornerCaseTest(-0.3);
505   quantizeBiasInt32CornerCaseTest(0.0000003);
506   quantizeBiasInt32CornerCaseTest(-0.0000003);
507   quantizeBiasInt32CornerCaseTest(30000000.0);
508   quantizeBiasInt32CornerCaseTest(-30000000.0);
509 }
510 
511 /// Verify the quantization utility function which performs finer grained
512 /// quantization along a given dimension for given \p qSchema and \p qTy.
513 template <class eTy>
quantizeTensorRowwise(quantization::Schema qSchema,ElemKind qTy)514 static void quantizeTensorRowwise(quantization::Schema qSchema, ElemKind qTy) {
515   dim_t numCols = 20;
516   dim_t qDim = 0;
517   dim_t qStep = 1;
518 
519   // Initialize tensors.
520   Tensor tensor(ElemKind::FloatTy, {2, numCols});
521   Tensor row1(ElemKind::FloatTy, {numCols});
522   Tensor row2(ElemKind::FloatTy, {numCols});
523   auto tensorH = tensor.getHandle<float>();
524   auto row1H = row1.getHandle<float>();
525   auto row2H = row2.getHandle<float>();
526   for (dim_t idx = 0; idx < numCols; idx++) {
527     tensorH.at({0, idx}) = float(idx);
528     tensorH.at({1, idx}) = float(idx) - 128.0;
529     row1H.raw(idx) = float(idx);
530     row2H.raw(idx) = float(idx) - 128.0;
531   }
532 
533   // Quantize rowwise using specialized function.
534   Tensor scales(ElemKind::FloatTy, {2});
535   Tensor offsets(ElemKind::Int32ITy, {2});
536   getTensorQuantizationParams(tensor, scales, offsets, qSchema, qTy, qDim,
537                               qStep);
538   Tensor tensorQ =
539       quantization::quantizeTensor(tensor, scales, offsets, qTy, qDim, qStep);
540   auto tensorQH = tensorQ.getHandle<eTy>();
541   auto scalesH = scales.getHandle<float>();
542   auto offsetsH = offsets.getHandle<int32_t>();
543 
544   // Quantize rowwise using per-tensor functions.
545   float row1Min = tensorH.at({0, 0});
546   float row1Max = tensorH.at({0, numCols - 1});
547   float row2Min = tensorH.at({1, 0});
548   float row2Max = tensorH.at({1, numCols - 1});
549   auto TQP1 =
550       quantization::chooseQuantizationParams({row1Min, row1Max}, qSchema, qTy);
551   auto TQP2 =
552       quantization::chooseQuantizationParams({row2Min, row2Max}, qSchema, qTy);
553   Tensor row1Q = quantization::quantizeTensor(row1, TQP1, qTy);
554   Tensor row2Q = quantization::quantizeTensor(row2, TQP2, qTy);
555   auto row1QH = row1Q.getHandle<eTy>();
556   auto row2QH = row2Q.getHandle<eTy>();
557 
558   // Check.
559   EXPECT_EQ(TQP1.scale, scalesH.raw(0));
560   EXPECT_EQ(TQP2.scale, scalesH.raw(1));
561   EXPECT_EQ(TQP1.offset, offsetsH.raw(0));
562   EXPECT_EQ(TQP2.offset, offsetsH.raw(1));
563   for (dim_t idx = 0; idx < 3; idx++) {
564     EXPECT_EQ(tensorQH.at({0, idx}), row1QH.raw(idx));
565     EXPECT_EQ(tensorQH.at({1, idx}), row2QH.raw(idx));
566   }
567 }
568 
TEST(Quantization,QuantizeTensorRowwiseTest)569 TEST(Quantization, QuantizeTensorRowwiseTest) {
570   quantizeTensorRowwise<int8_t>(quantization::Schema::Asymmetric,
571                                 ElemKind::Int8QTy);
572   quantizeTensorRowwise<int16_t>(quantization::Schema::Asymmetric,
573                                  ElemKind::Int16QTy);
574   quantizeTensorRowwise<int32_t>(quantization::Schema::Asymmetric,
575                                  ElemKind::Int32QTy);
576   quantizeTensorRowwise<int8_t>(quantization::Schema::Symmetric,
577                                 ElemKind::Int8QTy);
578   quantizeTensorRowwise<int16_t>(quantization::Schema::Symmetric,
579                                  ElemKind::Int16QTy);
580   quantizeTensorRowwise<int32_t>(quantization::Schema::Symmetric,
581                                  ElemKind::Int32QTy);
582   quantizeTensorRowwise<int8_t>(quantization::Schema::SymmetricWithUnsigned,
583                                 ElemKind::Int8QTy);
584   quantizeTensorRowwise<int16_t>(quantization::Schema::SymmetricWithUnsigned,
585                                  ElemKind::Int16QTy);
586   quantizeTensorRowwise<int32_t>(quantization::Schema::SymmetricWithUnsigned,
587                                  ElemKind::Int32QTy);
588   quantizeTensorRowwise<int8_t>(quantization::Schema::SymmetricWithPower2Scale,
589                                 ElemKind::Int8QTy);
590   quantizeTensorRowwise<int16_t>(quantization::Schema::SymmetricWithPower2Scale,
591                                  ElemKind::Int16QTy);
592   quantizeTensorRowwise<int32_t>(quantization::Schema::SymmetricWithPower2Scale,
593                                  ElemKind::Int32QTy);
594 }
595 
596 /// Helper for quantizing a simple Conv with precision \p quantizationPrecision
597 /// while the bias is quantized using \p quantizationPrecisionBias.
quantizeSimpleConvGraph(ElemKind quantizationPrecision,ElemKind quantizationPrecisionBias)598 static void quantizeSimpleConvGraph(ElemKind quantizationPrecision,
599                                     ElemKind quantizationPrecisionBias) {
600   ExecutionEngine EE{};
601   auto &mod = EE.getModule();
602   Function *F = mod.createFunction("main");
603 
604   auto *input =
605       mod.createPlaceholder(ElemKind::FloatTy, {1, 4, 4, 1}, "input", false);
606   auto *filter = mod.createConstant(ElemKind::FloatTy, {2, 2, 2, 1}, "filter");
607   auto *bias = mod.createConstant(ElemKind::FloatTy, {2}, "bias");
608   auto outTy = mod.uniqueType(ElemKind::FloatTy, {1, 4, 8, 2});
609   PlaceholderBindings bindings;
610   bindings.allocate(input);
611   filter->getHandle().randomize(-1.0, 1.0, mod.getPRNG());
612   bias->getHandle().randomize(-1.0, 1.0, mod.getPRNG());
613 
614   auto *CN = F->createConv("Conv", input, filter, bias, outTy, {2, 2}, {1, 1},
615                            {0, 2, 1, 3}, 1);
616   auto *S = F->createSave("ret", CN);
617   bindings.allocate(S->getPlaceholder());
618 
619   quantization::QuantizationConfiguration quantConfig{{
620       {input->getOutput().generateNodeOutputName(), {0.0f, 2.0f}},
621       {filter->getOutput().generateNodeOutputName(), {0.0f, 3.0f}},
622       {bias->getOutput().generateNodeOutputName(), {0.0f, 4.0f}},
623       {CN->getResult().generateNodeOutputName(), {0.0f, 6.0f}},
624   }};
625 
626   quantConfig.precision = quantizationPrecision;
627   quantConfig.precisionBias = quantizationPrecisionBias;
628   quantConfig.assertAllNodesQuantized = true;
629   std::unique_ptr<Backend> backend(createBackend(EE.getBackendName()));
630   quantization::quantizeFunction(F, quantConfig, *backend);
631 
632   // Make sure that graph can be compiled and run.
633   EE.compile(CompilationMode::Infer);
634   EE.run(bindings);
635 }
636 
637 /// Test that a simple Conv graph can be quantized in Int8QTy and Int8QTy bias.
TEST(Quantization,QuantizeGraph_Int8_BiasInt8)638 TEST(Quantization, QuantizeGraph_Int8_BiasInt8) {
639   quantizeSimpleConvGraph(ElemKind::Int8QTy, ElemKind::Int8QTy);
640 }
641 
642 /// Test that a simple Conv graph can be quantized in Int8QTy and Int32QTy bias.
TEST(Quantization,QuantizeGraph_Int8_BiasInt32)643 TEST(Quantization, QuantizeGraph_Int8_BiasInt32) {
644   quantizeSimpleConvGraph(ElemKind::Int8QTy, ElemKind::Int32QTy);
645 }
646 
647 /// Test that a simple Conv graph can be quantized in Int16QTy and Int16QTy
648 /// bias.
TEST(Quantization,QuantizeGraph_Int16_BiasInt16)649 TEST(Quantization, QuantizeGraph_Int16_BiasInt16) {
650   quantizeSimpleConvGraph(ElemKind::Int16QTy, ElemKind::Int16QTy);
651 }
652 
653 /// Test that a simple Conv graph can be quantized in Int16QTy and Int32QTy
654 /// bias.
TEST(Quantization,QuantizeGraph_Int16_BiasInt32)655 TEST(Quantization, QuantizeGraph_Int16_BiasInt32) {
656   quantizeSimpleConvGraph(ElemKind::Int16QTy, ElemKind::Int32QTy);
657 }
658 
659 /// Test that when a node is quantized before its users are quantized then the
660 /// users correctly find the quantization parameters. This tests that updating
661 /// the nodeToTQP_ map in FunctionQuantizer::postProcessing() works correctly.
TEST(Quantization,TestQuantizedInputBeforeQuantizedNode)662 TEST(Quantization, TestQuantizedInputBeforeQuantizedNode) {
663   ExecutionEngine EE{};
664   auto &mod = EE.getModule();
665   Function *F = mod.createFunction("main");
666 
667   auto *input = mod.createPlaceholder(ElemKind::FloatTy, {3}, "input", true);
668   PlaceholderBindings bindings;
669   bindings.allocate(input);
670 
671   // Note: Intentionally add successive reshapes so the GraphOptimizer merges
672   // them and creates a new one. This way the newly created Reshape will be
673   // placed at the end of the list of nodes in F, and then it will be quantized
674   // before SN. I think this is the most straightforward way to cover the logic
675   // path inside FunctionQuantizer::postProcessing() that updates nodeToTQP_.
676   auto *reshape1 = F->createReshape("reshape1", input, {3, 1});
677   auto *reshape2 = F->createReshape("reshape2", reshape1, {1, 3});
678   auto *SN = F->createSlice("slice", reshape2, {0, 1}, {1, 2});
679   auto *S = F->createSave("ret", SN);
680   bindings.allocate(S->getPlaceholder());
681 
682   // We need to optimize here first so that the two reshapes are merged.
683   optimize(F, CompilationMode::Infer);
684 
685   ReshapeNode *newReshape = llvm::dyn_cast<ReshapeNode>(SN->getInput());
686   ASSERT_TRUE(newReshape);
687 
688   quantization::QuantizationConfiguration quantConfig{{
689       {input->getOutput().generateNodeOutputName(), {-1.0, 1.0}},
690       {newReshape->getResult().generateNodeOutputName(), {-1.0, 1.0}},
691       {NodeValue::generateNodeOutputName(SN->getName()), {-1.0, 1.0}},
692   }};
693 
694   quantConfig.assertAllNodesQuantized = true;
695   std::unique_ptr<Backend> backend(createBackend(EE.getBackendName()));
696   quantization::quantizeFunction(F, quantConfig, *backend);
697 
698   // Remove unnecessary conversions.
699   optimize(F, CompilationMode::Infer);
700 
701   // Now we verify that the SliceNode was in fact quantized.
702   {
703     auto *saveNode = llvm::dyn_cast<SaveNode>(F->getNodeByName(S->getName()));
704     ASSERT_TRUE(saveNode);
705     auto *deqNode =
706         llvm::dyn_cast<DequantizeNode>(saveNode->getInput().getNode());
707     ASSERT_TRUE(deqNode);
708     auto *sliceNode = llvm::dyn_cast<SliceNode>(deqNode->getInput().getNode());
709     ASSERT_TRUE(sliceNode);
710     EXPECT_TRUE(sliceNode->getResult().getType()->isQuantizedType());
711   }
712 }
713 
714 /// Test enabling RowwiseQuantizedFullyConnected in Glow quantization
715 /// procedure. A FC can be quantized and converted to a
716 /// RowwiseQuantizedFullyConnected if:
717 /// 1. The weights of FC is constant;
718 /// 2. Use -enable-rowwise option or set enableRowwise param in
719 /// quantization::quantizeFunction to true. In unittest, the later one is used.
720 static void
enableRowwiseQuantizedFullyConnected(ElemKind quantizationPrecision,ElemKind quantizationPrecisionBias)721 enableRowwiseQuantizedFullyConnected(ElemKind quantizationPrecision,
722                                      ElemKind quantizationPrecisionBias) {
723   ExecutionEngine EE{};
724   auto &mod = EE.getModule();
725   Function *F = mod.createFunction("main");
726 
727   auto *input = mod.createPlaceholder(ElemKind::FloatTy, {1, 3}, "input", true);
728   auto *W = mod.createPlaceholder(ElemKind::FloatTy, {3, 2}, "weights", true);
729   auto *B = mod.createPlaceholder(ElemKind::FloatTy, {2}, "bias", true);
730   PlaceholderBindings bindings;
731   bindings.allocate(input);
732   bindings.allocate(W)->init(Tensor::InitKind::Xavier, 3, mod.getPRNG());
733   bindings.allocate(B)->init(Tensor::InitKind::Broadcast, 0.1, mod.getPRNG());
734 
735   auto *WC = mod.createConstant(ElemKind::FloatTy, W->dims(), "wc");
736   auto *FC = F->createFullyConnected("FC", input, WC, B);
737   auto *S = F->createSave("ret", FC);
738   bindings.allocate(S->getPlaceholder());
739 
740   LoweredInfoMap loweredMapForQuant;
741   CompilationContext cctx(/* bindings */ nullptr, &loweredMapForQuant);
742   ::glow::lower(F, cctx);
743 
744   // Get the MatMul node and the Batched_Add node.
745   MatMulNode *matMul;
746   BatchedAddNode *batchedAdd;
747   for (Node &N : F->getNodes()) {
748     if (N.getKind() == Kinded::Kind::MatMulNodeKind) {
749       matMul = llvm::cast<MatMulNode>(&N);
750     }
751     if (N.getKind() == Kinded::Kind::BatchedAddNodeKind) {
752       batchedAdd = llvm::cast<BatchedAddNode>(&N);
753     }
754   }
755   ASSERT_TRUE(matMul);
756   ASSERT_TRUE(batchedAdd);
757 
758   quantization::QuantizationConfiguration quantConfig{{
759       {input->getOutput().generateNodeOutputName(), {0.2f, 2.0f}},
760       {WC->getOutput().generateNodeOutputName(), {0.3f, 3.0f}},
761       {B->getOutput().generateNodeOutputName(), {0.4f, 4.0f}},
762       {matMul->getResult().generateNodeOutputName(), {0.6f, 6.0f}},
763       {batchedAdd->getResult().generateNodeOutputName(), {0.6f, 6.0f}},
764   }};
765 
766   quantConfig.precision = quantizationPrecision;
767   quantConfig.precisionBias = quantizationPrecisionBias;
768   quantConfig.enableRowwise = true;
769   quantConfig.assertAllNodesQuantized = true;
770   std::unique_ptr<Backend> backend(createBackend(EE.getBackendName()));
771   quantization::quantizeFunction(F, quantConfig, *backend, loweredMapForQuant);
772 
773   // Check the graph structure after quantization.
774   auto *saveNode = llvm::dyn_cast<SaveNode>(F->getNodeByName(S->getName()));
775   ASSERT_TRUE(saveNode);
776   auto *deqNode =
777       llvm::dyn_cast<DequantizeNode>(saveNode->getInput().getNode());
778   ASSERT_TRUE(deqNode);
779   auto *rwNode = llvm::dyn_cast<RowwiseQuantizedFullyConnectedNode>(
780       deqNode->getInput().getNode());
781   ASSERT_TRUE(rwNode);
782   auto *inNode = llvm::dyn_cast<QuantizeNode>(rwNode->getInput().getNode());
783   ASSERT_TRUE(inNode);
784   auto *biasNode = llvm::dyn_cast<QuantizeNode>(rwNode->getBias().getNode());
785   ASSERT_TRUE(biasNode);
786   auto *weightsNode = llvm::dyn_cast<Constant>(rwNode->getWeights().getNode());
787   ASSERT_TRUE(weightsNode);
788   auto *scalesNode = llvm::dyn_cast<Constant>(rwNode->getScales().getNode());
789   ASSERT_TRUE(scalesNode);
790   auto *offsetsNode = llvm::dyn_cast<Constant>(rwNode->getOffsets().getNode());
791   ASSERT_TRUE(offsetsNode);
792 
793   // Make sure that graph can be compiled and run. We check the correctness of
794   // RowwiseQuantizedFullyConnected in operatorTests.cpp.
795   EE.compile(CompilationMode::Infer);
796 
797   EE.run(bindings);
798 }
799 
TEST(Quantization,enableRowwiseQuantizedFullyConnected_Int8_BiasInt8)800 TEST(Quantization, enableRowwiseQuantizedFullyConnected_Int8_BiasInt8) {
801   enableRowwiseQuantizedFullyConnected(ElemKind::Int8QTy, ElemKind::Int8QTy);
802 }
803 
TEST(Quantization,enableRowwiseQuantizedFullyConnected_Int8_BiasInt32)804 TEST(Quantization, enableRowwiseQuantizedFullyConnected_Int8_BiasInt32) {
805   enableRowwiseQuantizedFullyConnected(ElemKind::Int8QTy, ElemKind::Int32QTy);
806 }
807 
808 /// Test enabling RowwiseQuantizedFullyConnected with Symmetric quantization.
TEST(Quantization,enableRowwiseQuantizedFullyConnectedSymmetric)809 TEST(Quantization, enableRowwiseQuantizedFullyConnectedSymmetric) {
810   ExecutionEngine EE{};
811   auto &mod = EE.getModule();
812   PlaceholderBindings bindings;
813   Function *F = mod.createFunction("main");
814 
815   auto *input = mod.createPlaceholder(ElemKind::FloatTy, {10, 80}, "in", false);
816   auto *FC = F->createFullyConnected(bindings, "FC", input, 100);
817   auto *res = F->createSave("save", FC);
818   bindings.allocate(res->getPlaceholder());
819   bindings.allocate(input);
820   bindings.get(input)->getHandle().randomize(-1.0, 6.0, mod.getPRNG());
821 
822   ::glow::convertPlaceholdersToConstants(F, bindings,
823                                          {input, res->getPlaceholder()});
824 
825   // Note that we generate values for the Weights because they will be used
826   // during rowwise-quantization to select each row's scale/offset.
827   auto *WC = llvm::cast<Constant>(FC->getWeights());
828   WC->getPayloadMutable().getHandle().randomize(-0.7, 1.1, mod.getPRNG());
829   auto *BC = llvm::cast<Constant>(FC->getBias());
830 
831   TensorProfilingParams inputTPP = {-1.0, 6.0};
832   TensorProfilingParams matmulTPP = {0.0, 10.0};
833   TensorProfilingParams batchedaddTPP = {0.0, 10.0};
834   TensorProfilingParams biasTPP = {0, 20};
835 
836   TensorQuantizationParams inputTQP = chooseQuantizationParams(
837       inputTPP, quantization::Schema::Symmetric, ElemKind::Int8QTy);
838   TensorQuantizationParams matmulTQP = chooseQuantizationParams(
839       matmulTPP, quantization::Schema::Symmetric, ElemKind::Int8QTy);
840   TensorQuantizationParams batchedaddTQP = chooseQuantizationParams(
841       batchedaddTPP, quantization::Schema::Symmetric, ElemKind::Int8QTy);
842   TensorQuantizationParams biasTQP = chooseQuantizationParams(
843       biasTPP, quantization::Schema::Symmetric, ElemKind::Int8QTy);
844 
845   EXPECT_EQ(inputTQP.offset, 0);
846   EXPECT_EQ(matmulTQP.offset, 0);
847   EXPECT_EQ(batchedaddTQP.offset, 0);
848   EXPECT_EQ(biasTQP.offset, 0);
849 
850   LoweredInfoMap loweredMapForQuant;
851   CompilationContext cctx(/* bindings */ nullptr, &loweredMapForQuant);
852   ::glow::lower(F, cctx);
853 
854   // Get the MatMul node and the Batched_Add node.
855   MatMulNode *matMul;
856   BatchedAddNode *batchedAdd;
857   for (Node &N : F->getNodes()) {
858     if (N.getKind() == Kinded::Kind::MatMulNodeKind) {
859       matMul = llvm::cast<MatMulNode>(&N);
860     }
861     if (N.getKind() == Kinded::Kind::BatchedAddNodeKind) {
862       batchedAdd = llvm::cast<BatchedAddNode>(&N);
863     }
864   }
865   ASSERT_TRUE(matMul);
866   ASSERT_TRUE(batchedAdd);
867 
868   // Note: Using dummy offset for the weights, as it should be
869   // rowwise-quantized.
870   quantization::QuantizationConfiguration quantConfig{{
871       {input->getOutput().generateNodeOutputName(), inputTPP},
872       {WC->getOutput().generateNodeOutputName(), {-0.7, 1.1}},
873       {BC->getOutput().generateNodeOutputName(), biasTPP},
874       {matMul->getResult().generateNodeOutputName(), matmulTPP},
875       {batchedAdd->getResult().generateNodeOutputName(), batchedaddTPP},
876   }};
877 
878   quantConfig.schema = quantization::Schema::Symmetric;
879   quantConfig.enableRowwise = true;
880   quantConfig.assertAllNodesQuantized = true;
881   std::unique_ptr<Backend> backend(createBackend(EE.getBackendName()));
882   quantization::quantizeFunction(F, quantConfig, *backend, loweredMapForQuant);
883 
884   // Check the graph structure after quantization.
885   auto *saveNode = llvm::dyn_cast<SaveNode>(F->getNodeByName(res->getName()));
886   ASSERT_TRUE(saveNode);
887   auto *deqNode =
888       llvm::dyn_cast<DequantizeNode>(saveNode->getInput().getNode());
889   ASSERT_TRUE(deqNode);
890   auto *rwNode = llvm::dyn_cast<RowwiseQuantizedFullyConnectedNode>(
891       deqNode->getInput().getNode());
892   ASSERT_TRUE(rwNode);
893   auto *inNode = llvm::dyn_cast<QuantizeNode>(rwNode->getInput().getNode());
894   ASSERT_TRUE(inNode);
895   auto *biasNode = llvm::dyn_cast<QuantizeNode>(rwNode->getBias().getNode());
896   ASSERT_TRUE(biasNode);
897   auto *weightsNode = llvm::dyn_cast<Constant>(rwNode->getWeights().getNode());
898   ASSERT_TRUE(weightsNode);
899   auto *scalesNode = llvm::dyn_cast<Constant>(rwNode->getScales().getNode());
900   ASSERT_TRUE(scalesNode);
901   auto *offsetsNode = llvm::dyn_cast<Constant>(rwNode->getOffsets().getNode());
902   ASSERT_TRUE(offsetsNode);
903 
904   // Because we're using symmetric quantization, the offsets should all be zero.
905   const auto offsetsH = offsetsNode->getPayload().getHandle<int32_t>();
906   EXPECT_TRUE(offsetsH.isZero());
907 
908   // Make sure that graph can be compiled and run. We check the correctness of
909   // RowwiseQuantizedFullyConnected in operatorTests.cpp.
910   EE.compile(CompilationMode::Infer);
911 
912   EE.run(bindings);
913 }
914 
915 /// Test enabling ChannelwiseQuantizedConv2D in the quantization procedure.
916 /// A standard Convolution node can be quantized and converted to a
917 /// ChannelwiseQuantizedConvolution if:
918 /// 1. The filter and bias are constants.
919 /// 2. Use -enable-channelwise option or set enableChannelwise param in
920 /// quantization::quantizeFunction to true.
enableChannelwiseQuantizedConv2D(ElemKind qPrec,ElemKind qPrecBias,quantization::Schema schema)921 static void enableChannelwiseQuantizedConv2D(ElemKind qPrec, ElemKind qPrecBias,
922                                              quantization::Schema schema) {
923   ExecutionEngine EE{};
924   auto &mod = EE.getModule();
925   Function *F = mod.createFunction("main");
926   PlaceholderBindings bindings;
927 
928   // Convolution parameters.
929   std::vector<dim_t> inputDims = {5, 3, 3, 2};
930   std::vector<dim_t> filterDims = {4, 2, 2, 1};
931   std::vector<dim_t> biasDims = {4};
932   std::vector<dim_t> outputDims = {5, 2, 2, 4};
933   std::vector<unsigned_t> kernels = {2, 2};
934   std::vector<unsigned_t> strides = {1, 1};
935   std::vector<unsigned_t> pads = {0, 0, 0, 0};
936   dim_t group = 2;
937   dim_t dilation = 1;
938 
939   // Create input placeholder.
940   auto *input =
941       mod.createPlaceholder(ElemKind::FloatTy, inputDims, "input", false);
942   bindings.allocate(input)->getHandle<float>().randomize(-1.0, 1.0,
943                                                          mod.getPRNG());
944 
945   // Create filter constant.
946   auto *filterC = mod.createConstant(ElemKind::FloatTy, filterDims, "filterC");
947   filterC->getPayloadMutable().getHandle<float>().randomize(-1.0, 1.0,
948                                                             mod.getPRNG());
949 
950   // Create bias constant.
951   auto *biasC = mod.createConstant(ElemKind::FloatTy, biasDims, "biasC");
952   biasC->getPayloadMutable().getHandle<float>().randomize(-1.0, 1.0,
953                                                           mod.getPRNG());
954 
955   // Create Convolution.
956   auto *outTy = mod.uniqueType(ElemKind::FloatTy, outputDims);
957   ConvolutionNode *conv =
958       F->createConv("Conv", input, filterC, biasC, outTy, kernels, strides,
959                     pads, group, dilation);
960   SaveNode *save = F->createSave("save", conv);
961   bindings.allocate(save->getPlaceholder());
962 
963   // Quantize function. Choose asymmetric ranges to test quantization params.
964   quantization::QuantizationConfiguration quantConfig{{
965       {input->getOutput().generateNodeOutputName(), {-2.0, 1.0}},
966       {filterC->getOutput().generateNodeOutputName(), {-1.0, 2.0}},
967       {biasC->getOutput().generateNodeOutputName(), {0.0, 3.0}},
968       {conv->getResult().generateNodeOutputName(), {-3.0, 0.0}},
969   }};
970   quantConfig.schema = schema;
971   quantConfig.precision = qPrec;
972   quantConfig.precisionBias = qPrecBias;
973   quantConfig.enableChannelwise = true;
974   quantConfig.assertAllNodesQuantized = true;
975   std::unique_ptr<Backend> backend(createBackend(EE.getBackendName()));
976   quantization::quantizeFunction(F, quantConfig, *backend);
977 
978   // Check the graph structure after quantization.
979   auto *saveNode = llvm::dyn_cast<SaveNode>(F->getNodeByName(save->getName()));
980   ASSERT_TRUE(saveNode);
981   auto *deqNode =
982       llvm::dyn_cast<DequantizeNode>(saveNode->getInput().getNode());
983   ASSERT_TRUE(deqNode);
984   auto *cwqConvNode = llvm::dyn_cast<ChannelwiseQuantizedConvolutionNode>(
985       deqNode->getInput().getNode());
986   ASSERT_TRUE(cwqConvNode);
987   auto *inputNode =
988       llvm::dyn_cast<QuantizeNode>(cwqConvNode->getInput().getNode());
989   ASSERT_TRUE(inputNode);
990   auto *filterNode =
991       llvm::dyn_cast<Constant>(cwqConvNode->getFilter().getNode());
992   ASSERT_TRUE(filterNode);
993   auto *biasNode = llvm::dyn_cast<Constant>(cwqConvNode->getBias().getNode());
994   ASSERT_TRUE(biasNode);
995   auto *filterScalesNode =
996       llvm::dyn_cast<Constant>(cwqConvNode->getFilterScales().getNode());
997   ASSERT_TRUE(filterScalesNode);
998   auto *filterOffsetsNode =
999       llvm::dyn_cast<Constant>(cwqConvNode->getFilterOffsets().getNode());
1000   ASSERT_TRUE(filterOffsetsNode);
1001   auto *biasScalesNode =
1002       llvm::dyn_cast<Constant>(cwqConvNode->getBiasScales().getNode());
1003   ASSERT_TRUE(biasScalesNode);
1004   auto *biasOffsetsNode =
1005       llvm::dyn_cast<Constant>(cwqConvNode->getBiasOffsets().getNode());
1006   ASSERT_TRUE(biasOffsetsNode);
1007 
1008   // Check precisions.
1009   ASSERT_EQ(inputNode->getResult().getElementType(), qPrec);
1010   ASSERT_EQ(filterNode->getOutput().getElementType(), qPrec);
1011   ASSERT_EQ(biasNode->getOutput().getElementType(), qPrecBias);
1012   ASSERT_EQ(filterScalesNode->getOutput().getElementType(), ElemKind::FloatTy);
1013   ASSERT_EQ(filterOffsetsNode->getOutput().getElementType(),
1014             ElemKind::Int32ITy);
1015   ASSERT_EQ(biasScalesNode->getOutput().getElementType(), ElemKind::FloatTy);
1016   ASSERT_EQ(biasOffsetsNode->getOutput().getElementType(), ElemKind::Int32ITy);
1017   ASSERT_EQ(cwqConvNode->getResult().getElementType(), qPrec);
1018 
1019   // Check quantization parameters.
1020   validateQuantizationParams({inputNode->getResult().getType()->getScale(),
1021                               inputNode->getResult().getType()->getOffset()},
1022                              schema, qPrec);
1023   validateQuantizationParams({cwqConvNode->getResult().getType()->getScale(),
1024                               cwqConvNode->getResult().getType()->getOffset()},
1025                              schema, qPrec);
1026   for (dim_t idx = 0; idx < outputDims[3]; idx++) {
1027     auto filterScalesH = filterScalesNode->getPayload().getHandle<float>();
1028     auto filterOffsetsH = filterOffsetsNode->getPayload().getHandle<int32_t>();
1029     auto biasScalesH = biasScalesNode->getPayload().getHandle<float>();
1030     auto biasOffsetsH = biasOffsetsNode->getPayload().getHandle<int32_t>();
1031     validateQuantizationParams(
1032         {filterScalesH.raw(idx), filterOffsetsH.raw(idx)}, schema, qPrec);
1033     validateQuantizationParams({biasScalesH.raw(idx), biasOffsetsH.raw(idx)},
1034                                schema, qPrecBias);
1035   }
1036 
1037   // Make sure that graph can be compiled and run. We check the correctness of
1038   // ChannelwiseQuantizedConvolution in OperatorTest.cpp.
1039   EE.compile(CompilationMode::Infer);
1040   EE.run(bindings);
1041 }
1042 
TEST(Quantization,enableChannelwiseQuantizedConv2D_Int8_BiasInt8)1043 TEST(Quantization, enableChannelwiseQuantizedConv2D_Int8_BiasInt8) {
1044   enableChannelwiseQuantizedConv2D(ElemKind::Int8QTy, ElemKind::Int8QTy,
1045                                    quantization::Schema::Asymmetric);
1046   enableChannelwiseQuantizedConv2D(ElemKind::Int8QTy, ElemKind::Int8QTy,
1047                                    quantization::Schema::Symmetric);
1048   enableChannelwiseQuantizedConv2D(ElemKind::Int8QTy, ElemKind::Int8QTy,
1049                                    quantization::Schema::SymmetricWithUnsigned);
1050   enableChannelwiseQuantizedConv2D(
1051       ElemKind::Int8QTy, ElemKind::Int8QTy,
1052       quantization::Schema::SymmetricWithPower2Scale);
1053 }
1054 
TEST(Quantization,enableChannelwiseQuantizedConv2D_Int8_BiasInt32)1055 TEST(Quantization, enableChannelwiseQuantizedConv2D_Int8_BiasInt32) {
1056   enableChannelwiseQuantizedConv2D(ElemKind::Int8QTy, ElemKind::Int32QTy,
1057                                    quantization::Schema::Asymmetric);
1058   enableChannelwiseQuantizedConv2D(ElemKind::Int8QTy, ElemKind::Int32QTy,
1059                                    quantization::Schema::Symmetric);
1060   enableChannelwiseQuantizedConv2D(ElemKind::Int8QTy, ElemKind::Int32QTy,
1061                                    quantization::Schema::SymmetricWithUnsigned);
1062   enableChannelwiseQuantizedConv2D(
1063       ElemKind::Int8QTy, ElemKind::Int32QTy,
1064       quantization::Schema::SymmetricWithPower2Scale);
1065 }
1066 
1067 /// Check that SLWS is correctly fused rowwise-quantized by the quantizer.
TEST(Quantization,enableRowwiseQuantizedSLWS)1068 TEST(Quantization, enableRowwiseQuantizedSLWS) {
1069   ExecutionEngine EE{};
1070   auto &mod = EE.getModule();
1071   Function *F = mod.createFunction("main");
1072   PlaceholderBindings bindings;
1073 
1074   auto *data = mod.createPlaceholder(ElemKind::FloatTy, {3, 1}, "data", false);
1075   auto *weights =
1076       mod.createPlaceholder(ElemKind::FloatTy, {8}, "weights", false);
1077   auto *indices =
1078       mod.createPlaceholder(ElemKind::Int64ITy, {8}, "indices", false);
1079   auto *lengths =
1080       mod.createPlaceholder(ElemKind::Int32ITy, {4}, "lengths", false);
1081 
1082   // Don't worry about allocating them as we are not going to run anyway.
1083   bindings.allocate(data);
1084   bindings.allocate(weights);
1085   bindings.allocate(indices);
1086   bindings.allocate(lengths);
1087 
1088   auto *SLWS = F->createSparseLengthsWeightedSum("SLWS", data, weights, indices,
1089                                                  lengths);
1090   auto *res = F->createSave("save", SLWS);
1091   ::glow::convertPlaceholdersToConstants(
1092       F, bindings, {indices, lengths, res->getPlaceholder()});
1093   bindings.allocate(res->getPlaceholder());
1094 
1095   quantization::QuantizationConfiguration quantConfig{{
1096       {SLWS->getData().generateNodeOutputName(), {0.2f, 2.0f}},
1097       {SLWS->getWeights().generateNodeOutputName(), {0.3f, 3.0f}},
1098       {SLWS->getResult().generateNodeOutputName(), {0.4f, 4.0f}},
1099   }};
1100 
1101   quantConfig.enableRowwise = true;
1102   quantConfig.assertAllNodesQuantized = true;
1103   std::unique_ptr<Backend> backend(createBackend(EE.getBackendName()));
1104   quantization::quantizeFunction(F, quantConfig, *backend);
1105   std::string saveName = std::string(res->getName());
1106   EE.compile(CompilationMode::Infer);
1107 
1108   // Check the graph structure after quantization.
1109   F = EE.getModule().getFunctions().front();
1110   auto *saveNode = llvm::dyn_cast<SaveNode>(F->getNodeByName(saveName));
1111   ASSERT_TRUE(saveNode);
1112   auto *FRWQSLWS =
1113       llvm::dyn_cast<FusedRowwiseQuantizedSparseLengthsWeightedSumNode>(
1114           saveNode->getInput().getNode());
1115   ASSERT_TRUE(FRWQSLWS);
1116 }
1117 
1118 /// Quantize ReLU node and make sure that quantized version
1119 /// has quantization parameters mapping to non-negative floating
1120 /// point range.
TEST(Quantization,quantizeReLU)1121 TEST(Quantization, quantizeReLU) {
1122   ExecutionEngine EE{};
1123   std::unique_ptr<Backend> backend(new MockQuantBackend);
1124   EE.setBackendName("Interpreter");
1125   auto &mod = EE.getModule();
1126   Function *F = mod.createFunction("main");
1127   auto *input = mod.createPlaceholder(ElemKind::FloatTy, {1, 3}, "input", true);
1128   auto *relu = F->createRELU("ReLU", input);
1129   PlaceholderBindings bindings;
1130   auto *ret = F->createSave("ret", relu);
1131   std::string retName = std::string(ret->getName());
1132   // Make sure that offset quantization parameter of ReLU is set
1133   // such that it produces non-negative floating point range.
1134   quantization::QuantizationConfiguration quantConfig{
1135       {{input->getOutput().generateNodeOutputName(), {0.2f, 2.0f}},
1136        {relu->getResult().generateNodeOutputName(), {0.0f, 3.0f}}}};
1137   quantConfig.assertAllNodesQuantized = true;
1138   quantization::quantizeFunction(F, quantConfig, *backend);
1139   EE.compile(CompilationMode::Infer);
1140 
1141   // Compute tensor quantization parameters for verification.
1142   auto reluTQP = chooseQuantizationParams({0.0f, 3.0f}, quantConfig.schema,
1143                                           quantConfig.precision);
1144 
1145   F = EE.getModule().getFunctions().front();
1146   auto *save = llvm::cast<SaveNode>(F->getNodeByName(retName));
1147   ASSERT_TRUE(llvm::isa<DequantizeNode>(save->getInput().getNode()));
1148   auto *dequantize = llvm::cast<DequantizeNode>(save->getInput().getNode());
1149   ASSERT_TRUE(llvm::isa<MaxNode>(dequantize->getInput().getNode()));
1150 
1151   MaxNode *max = llvm::cast<MaxNode>(dequantize->getInput().getNode());
1152   ASSERT_TRUE(max->getResult().getType()->isQuantizedType());
1153   EXPECT_EQ(max->getResult().getType()->getOffset(), reluTQP.offset);
1154   EXPECT_EQ(max->getResult().getType()->getScale(), reluTQP.scale);
1155 }
1156 
1157 /// Quantize Log, Sigmoid, and Tanh nodes and make sure that quantized versions
1158 /// are implemented as IntLookupTables, because the Interpreter only supports
1159 /// them as such.
TEST(Quantization,quantizeLookupTables)1160 TEST(Quantization, quantizeLookupTables) {
1161   ExecutionEngine EE{};
1162   auto &mod = EE.getModule();
1163   Function *F = mod.createFunction("main");
1164   auto *input = mod.createPlaceholder(ElemKind::FloatTy, {1, 3}, "input", true);
1165   auto *LN = F->createLog("log", input);
1166   auto *SN = F->createSigmoid("sigmoid", LN);
1167   auto *TN = F->createTanh("tanh", SN);
1168   auto *ret = F->createSave("ret", TN);
1169 
1170   quantization::QuantizationConfiguration quantConfig{
1171       {{input->getOutput().generateNodeOutputName(), {0.2f, 2.0f}},
1172        {LN->getResult().generateNodeOutputName(LN->getName()), {0.3f, 3.0f}},
1173        {SN->getResult().generateNodeOutputName(), {0.4f, 4.0f}},
1174        {TN->getResult().generateNodeOutputName(), {0.5f, 5.0f}}}};
1175   quantConfig.assertAllNodesQuantized = true;
1176   std::unique_ptr<Backend> backend(createBackend(EE.getBackendName()));
1177   quantization::quantizeFunction(F, quantConfig, *backend);
1178   optimize(F, CompilationMode::Infer);
1179 
1180   // Compute the quantization parameters based on the requirements of the
1181   // Sigmoid/Tanh or on the input/output values for Log.
1182   auto logInpTQP = chooseQuantizationParams({0.2, 2.0}, quantConfig.schema,
1183                                             quantConfig.precision);
1184   auto logOutTQP = chooseQuantizationParams({0.3, 3.0}, quantConfig.schema,
1185                                             quantConfig.precision);
1186   auto sigmoidInpTQP = chooseQuantizationParams({-6.0, 6.0}, quantConfig.schema,
1187                                                 quantConfig.precision);
1188   auto sigmoidOutTQP = chooseQuantizationParams({0.0, 1.0}, quantConfig.schema,
1189                                                 quantConfig.precision);
1190   auto tanhInpTQP = chooseQuantizationParams({-3.0, 3.0}, quantConfig.schema,
1191                                              quantConfig.precision);
1192   auto tanhOutTQP = chooseQuantizationParams({-1.0, 1.0}, quantConfig.schema,
1193                                              quantConfig.precision);
1194 
1195   auto *save = llvm::cast<SaveNode>(F->getNodeByName(ret->getName()));
1196   auto *dequantizeTanh =
1197       llvm::dyn_cast<DequantizeNode>(save->getInput().getNode());
1198   ASSERT_TRUE(dequantizeTanh);
1199   auto *tanhILT =
1200       llvm::dyn_cast<IntLookupTableNode>(dequantizeTanh->getInput().getNode());
1201   ASSERT_TRUE(tanhILT);
1202   EXPECT_FLOAT_EQ(tanhILT->getResult().getType()->getScale(), tanhOutTQP.scale);
1203   EXPECT_EQ(tanhILT->getResult().getType()->getOffset(), tanhOutTQP.offset);
1204   EXPECT_FLOAT_EQ(tanhILT->getInput().getType()->getScale(), tanhInpTQP.scale);
1205   EXPECT_EQ(tanhILT->getInput().getType()->getOffset(), tanhInpTQP.offset);
1206 
1207   auto *rescaleSigmoid =
1208       llvm::dyn_cast<RescaleQuantizedNode>(tanhILT->getInput().getNode());
1209   ASSERT_TRUE(rescaleSigmoid);
1210   auto *sigmoidILT =
1211       llvm::dyn_cast<IntLookupTableNode>(rescaleSigmoid->getInput().getNode());
1212   ASSERT_TRUE(sigmoidILT);
1213   EXPECT_FLOAT_EQ(sigmoidILT->getResult().getType()->getScale(),
1214                   sigmoidOutTQP.scale);
1215   EXPECT_EQ(sigmoidILT->getResult().getType()->getOffset(),
1216             sigmoidOutTQP.offset);
1217   EXPECT_FLOAT_EQ(sigmoidILT->getInput().getType()->getScale(),
1218                   sigmoidInpTQP.scale);
1219   EXPECT_EQ(sigmoidILT->getInput().getType()->getOffset(),
1220             sigmoidInpTQP.offset);
1221 
1222   auto *rescaleLog =
1223       llvm::dyn_cast<RescaleQuantizedNode>(sigmoidILT->getInput().getNode());
1224   ASSERT_TRUE(rescaleLog);
1225   auto *logILT =
1226       llvm::dyn_cast<IntLookupTableNode>(rescaleLog->getInput().getNode());
1227   ASSERT_TRUE(logILT);
1228   EXPECT_FLOAT_EQ(logILT->getResult().getType()->getScale(), logOutTQP.scale);
1229   EXPECT_EQ(logILT->getResult().getType()->getOffset(), logOutTQP.offset);
1230   EXPECT_FLOAT_EQ(logILT->getInput().getType()->getScale(), logInpTQP.scale);
1231   EXPECT_EQ(logILT->getInput().getType()->getOffset(), logInpTQP.offset);
1232 }
1233 
1234 /// Quantize Log, Sigmoid, and Tanh nodes and make sure that they are not
1235 /// replaced by LookupTables because the backend supports them directly.
TEST(Quantization,quantizeWithoutLookupTables)1236 TEST(Quantization, quantizeWithoutLookupTables) {
1237   ExecutionEngine EE{};
1238   std::unique_ptr<Backend> backend(new MockQuantBackend);
1239   EE.setBackendName("Interpreter");
1240   auto &mod = EE.getModule();
1241   Function *F = mod.createFunction("main");
1242   auto *input = mod.createPlaceholder(ElemKind::FloatTy, {1, 3}, "input", true);
1243   auto *LN = F->createLog("log", input);
1244   auto *SN = F->createSigmoid("sigmoid", LN);
1245   auto *TN = F->createTanh("tanh", SN);
1246   auto *ret = F->createSave("ret", TN);
1247 
1248   quantization::QuantizationConfiguration quantConfig{
1249       {{input->getOutput().generateNodeOutputName(), {0.2f, 2.0f}},
1250        {LN->getResult().generateNodeOutputName(), {0.3f, 3.0f}},
1251        {SN->getResult().generateNodeOutputName(), {0.4f, 4.0f}},
1252        {TN->getResult().generateNodeOutputName(), {0.5f, 5.0f}}}};
1253   quantConfig.assertAllNodesQuantized = true;
1254   quantization::quantizeFunction(F, quantConfig, *backend);
1255   optimize(F, CompilationMode::Infer);
1256 
1257   // Compute the quantization parameters for validation.
1258   auto logInpTQP = chooseQuantizationParams({0.2, 2.0}, quantConfig.schema,
1259                                             quantConfig.precision);
1260   auto logOutTQP = chooseQuantizationParams({0.3, 3.0}, quantConfig.schema,
1261                                             quantConfig.precision);
1262   auto sigmoidInpTQP = chooseQuantizationParams({0.3, 3.0}, quantConfig.schema,
1263                                                 quantConfig.precision);
1264   auto sigmoidOutTQP = chooseQuantizationParams(
1265       {0.4f, 4.0f}, quantConfig.schema, quantConfig.precision);
1266   auto tanhInpTQP = chooseQuantizationParams({0.4f, 4.0f}, quantConfig.schema,
1267                                              quantConfig.precision);
1268   auto tanhOutTQP = chooseQuantizationParams({0.5f, 5.0f}, quantConfig.schema,
1269                                              quantConfig.precision);
1270 
1271   auto *save = llvm::cast<SaveNode>(F->getNodeByName(ret->getName()));
1272   auto *dequantize = llvm::dyn_cast<DequantizeNode>(save->getInput().getNode());
1273   ASSERT_TRUE(dequantize);
1274   auto *tanh = llvm::dyn_cast<TanhNode>(dequantize->getInput());
1275   ASSERT_TRUE(tanh);
1276   EXPECT_FLOAT_EQ(tanh->getResult().getType()->getScale(), tanhOutTQP.scale);
1277   EXPECT_EQ(tanh->getResult().getType()->getOffset(), tanhOutTQP.offset);
1278   EXPECT_FLOAT_EQ(tanh->getInput().getType()->getScale(), tanhInpTQP.scale);
1279   EXPECT_EQ(tanh->getInput().getType()->getOffset(), tanhInpTQP.offset);
1280 
1281   auto *sigmoid = llvm::dyn_cast<SigmoidNode>(tanh->getInput());
1282   ASSERT_TRUE(sigmoid);
1283   EXPECT_FLOAT_EQ(sigmoid->getResult().getType()->getScale(),
1284                   sigmoidOutTQP.scale);
1285   EXPECT_EQ(sigmoid->getResult().getType()->getOffset(), sigmoidOutTQP.offset);
1286   EXPECT_FLOAT_EQ(sigmoid->getInput().getType()->getScale(),
1287                   sigmoidInpTQP.scale);
1288   EXPECT_EQ(sigmoid->getInput().getType()->getOffset(), sigmoidInpTQP.offset);
1289 
1290   auto *log = llvm::dyn_cast<LogNode>(sigmoid->getInput());
1291   ASSERT_TRUE(log);
1292   EXPECT_FLOAT_EQ(log->getResult().getType()->getScale(), logOutTQP.scale);
1293   EXPECT_EQ(log->getResult().getType()->getOffset(), logOutTQP.offset);
1294   EXPECT_FLOAT_EQ(log->getInput().getType()->getScale(), logInpTQP.scale);
1295   EXPECT_EQ(log->getInput().getType()->getOffset(), logInpTQP.offset);
1296 }
1297 
1298 /// Fills the tensor \p H with some stable random data with the seed \p seed
1299 /// and the range [-scale .. scale].
fillStableRandomData(Handle<float> H,size_t seed,float scale=1)1300 static void fillStableRandomData(Handle<float> H, size_t seed,
1301                                  float scale = 1) {
1302   for (size_t i = 0, e = H.size(); i < e; i++) {
1303     H.raw(i) = scale * (float((int(i * 1921 + seed) % 100) - 50) / 50);
1304   }
1305 }
1306 
1307 /// Builds a simple graph, returns back input var and save node through refs.
createSimpleGraphForQuantization(Module * M,PlaceholderBindings & bindings,Placeholder * A,Placeholder * B,llvm::StringRef funcName)1308 static Function *createSimpleGraphForQuantization(Module *M,
1309                                                   PlaceholderBindings &bindings,
1310                                                   Placeholder *A,
1311                                                   Placeholder *B,
1312                                                   llvm::StringRef funcName) {
1313   Function *F = M->createFunction(funcName);
1314 
1315   fillStableRandomData(bindings.allocate(A)->getHandle(), 1100, 1);
1316 
1317   fillStableRandomData(bindings.allocate(B)->getHandle(), 2001, 1);
1318 
1319   ConvolutionNode *CV = F->createConv(bindings, "conv", A, 16, 5, 1, 2, 2);
1320   auto *bias = cast<Placeholder>(CV->getBias());
1321   auto *filter = cast<Placeholder>(CV->getFilter());
1322   fillStableRandomData(bindings.get(bias)->getHandle(), 2001, 1);
1323   fillStableRandomData(bindings.get(filter)->getHandle(), 1000, 1);
1324 
1325   auto *RL = F->createRELU("relu", CV);
1326   auto *MP = F->createMaxPool("maxPool", RL, 2, 2, 1);
1327   // Just add noop transpose.
1328   auto *T = F->createTranspose("transpose", MP->getResult(), {0, 1, 2, 3});
1329   // Noop reshape, make sure conversion quantization procedure works well.
1330   auto *R = F->createReshape("reshape", T, T->getResult().dims());
1331   auto *AP = F->createAvgPool("avgPool", R, 2, 2, 1);
1332 
1333   FullyConnectedNode *FC = F->createFullyConnected(bindings, "fc", AP, 10);
1334 
1335   // Noop slice, make sure conversion quantization procedure works well.
1336   auto *S =
1337       F->createSlice("slice", FC, {0, 1},
1338                      {FC->getResult().dims()[0], FC->getResult().dims()[1]});
1339   auto *bias2 = cast<Placeholder>(FC->getBias());
1340   auto *filter2 = cast<Placeholder>(FC->getWeights());
1341 
1342   fillStableRandomData(bindings.get(bias2)->getHandle(), 3001, 1);
1343   fillStableRandomData(bindings.get(filter2)->getHandle(), 4000, 1);
1344 
1345   auto *CN = F->createConcat("concat", {S, B}, 0);
1346   auto *SP = F->createSplat("splat", B->getType(), 10.0);
1347   auto *O = F->createConcat("concat", {CN, SP}, 0);
1348   auto *TN = F->createTranspose("transpose", O, {1, 0});
1349   auto *BRAN = F->createBatchedReduceAdd("batchedreduceadd", TN, 0);
1350   auto *TLN = F->createTile("tile", BRAN, 2, 0);
1351   auto *SN = F->createSplat("splat", TLN->getResult().getType(), 100.0);
1352   auto *MN = F->createMax("max", SN, TLN);
1353   auto *CLTE = F->createCmpLTE("cmplte", MN, SN);
1354   auto *SLN = F->createSelect("select", CLTE, SN, MN);
1355   auto *save = F->createSave("save", SLN);
1356   bindings.allocate(save->getPlaceholder());
1357   return F;
1358 }
1359 
1360 /// Helper for an end to end test profiling a model on \p profileEE, then
1361 /// quantizing and running it on \p backendSpecificEE, quantizing with precision
1362 /// \p quantizationPrecision and disabling quantization for all Kinds in
1363 /// \p keepOriginalPrecisionForNodes. Results are compared from the profiling
1364 /// run and quantization run.
1365 static void
testQuantizationEnd2End(ExecutionEngine & profileEE,ExecutionEngine & backendSpecificEE,ElemKind quantizationPrecision,const KindSet & keepOriginalPrecisionForNodes={})1366 testQuantizationEnd2End(ExecutionEngine &profileEE,
1367                         ExecutionEngine &backendSpecificEE,
1368                         ElemKind quantizationPrecision,
1369                         const KindSet &keepOriginalPrecisionForNodes = {}) {
1370   auto *mod = &profileEE.getModule();
1371   auto *modBackend = &backendSpecificEE.getModule();
1372   PlaceholderBindings bindings;
1373   PlaceholderBindings bindingsBackend;
1374 
1375   auto *A =
1376       mod->createPlaceholder(ElemKind::FloatTy, {1, 32, 32, 2}, "A", false);
1377   auto *B = mod->createPlaceholder(ElemKind::FloatTy, {10, 9}, "B", false);
1378   auto *AB = modBackend->createPlaceholder(ElemKind::FloatTy, {1, 32, 32, 2},
1379                                            "A", false);
1380   auto *BB =
1381       modBackend->createPlaceholder(ElemKind::FloatTy, {10, 9}, "B", false);
1382 
1383   // STEP1 - Generate the first network to record the quantization parameters.
1384   createSimpleGraphForQuantization(mod, bindings, A, B, "main");
1385   createSimpleGraphForQuantization(modBackend, bindingsBackend, AB, BB, "main");
1386 
1387   LoweredInfoMap loweredMapForProf;
1388   CompilationContext cctxProf{&bindings, &loweredMapForProf};
1389   cctxProf.precisionConfig.quantMode = QuantizationMode::Profile;
1390   profileEE.compile(cctxProf);
1391   bindings.allocate(mod->getPlaceholders());
1392 
1393   // Run graph to capture profile.
1394   profileEE.run(bindings, "main");
1395 
1396   // STEP2 - Use the profile to quantize a network.
1397   LoweredInfoMap loweredMapForQuant;
1398   CompilationContext cctxQuant{&bindings, &loweredMapForQuant};
1399 
1400   // Get quantization infos and build new quantized graph.
1401   PrecisionConfiguration &precConfig = cctxQuant.precisionConfig;
1402   precConfig.quantMode = QuantizationMode::Quantize;
1403   precConfig.quantConfig.infos = quantization::generateNodeProfilingInfos(
1404       bindings, mod->getFunctions().front(), loweredMapForProf);
1405   precConfig.quantConfig.precision = quantizationPrecision;
1406   precConfig.quantConfig.assertAllNodesQuantized = true;
1407   precConfig.precisionModeKindSet = keepOriginalPrecisionForNodes;
1408 
1409   backendSpecificEE.compile(cctxQuant);
1410   bindingsBackend.allocate(modBackend->getPlaceholders());
1411   backendSpecificEE.run(bindingsBackend);
1412 
1413   // STEP3 - Compare the results of the original and quantized functions.
1414   auto result1Handle =
1415       bindings.get(bindings.getPlaceholderByNameSlow("save"))->getHandle();
1416   auto result2Handle =
1417       bindingsBackend.get(bindingsBackend.getPlaceholderByNameSlow("save"))
1418           ->getHandle();
1419 
1420   EXPECT_EQ(result1Handle.size(), result2Handle.size());
1421 
1422   for (int i = 0, e = result1Handle.size(); i < e; ++i) {
1423     float mx = result2Handle.raw(result2Handle.minMaxArg().second);
1424     double diff = std::fabs(result2Handle.raw(i) - result1Handle.raw(i)) / mx;
1425 
1426     // Allow 3% difference.
1427     EXPECT_NEAR(diff, 0, 0.03);
1428   }
1429 }
1430 
1431 /// End to end quantization test for Int8 quantization.
TEST_P(Operator,end2endInt8)1432 TEST_P(Operator, end2endInt8) {
1433   // The OpenCL backend does not support some of the nodes in the test;
1434   // explicitly whitelist them here as staying in float, so that the quantizer
1435   // does not complain.
1436   KindSet keepOriginalPrecisionForNodes;
1437   if (backendSpecificEE.getBackendName() == "OpenCL") {
1438     keepOriginalPrecisionForNodes.insert(Kinded::Kind::SelectNodeKind);
1439     keepOriginalPrecisionForNodes.insert(Kinded::Kind::CmpLTENodeKind);
1440     keepOriginalPrecisionForNodes.insert(
1441         Kinded::Kind::BatchedReduceAddNodeKind);
1442   }
1443 
1444   testQuantizationEnd2End(profileEE, backendSpecificEE, ElemKind::Int8QTy,
1445                           keepOriginalPrecisionForNodes);
1446 }
1447 
1448 /// Fills the tensor \p H with some stable random integers with the seed \p seed
1449 /// and the range [0, scale).
fillStableRandomIndex(Handle<int64_t> H,size_t seed,size_t scale=10)1450 static void fillStableRandomIndex(Handle<int64_t> H, size_t seed,
1451                                   size_t scale = 10) {
1452   for (size_t i = 0, e = H.size(); i < e; i++) {
1453     H.raw(i) = int(i * 1921 + seed) % scale;
1454   }
1455 }
1456 
1457 /// Builds a graph with two GRUs and saves output from last hidden node.
createGRUForQuantization(Module * M,PlaceholderBindings & bindings,llvm::StringRef funcName)1458 static Function *createGRUForQuantization(Module *M,
1459                                           PlaceholderBindings &bindings,
1460                                           llvm::StringRef funcName) {
1461   Function *F = M->createFunction(funcName);
1462 
1463   constexpr unsigned sequenceSize = 2;
1464   constexpr unsigned embeddingSize = 10;
1465   constexpr unsigned languageSize = 10;
1466   constexpr unsigned batchSize = 5;
1467   constexpr unsigned hiddenSize = 3 * embeddingSize;
1468 
1469   // STEP1 - Initialize inputs into GRU
1470   auto *emb = F->getParent()->createPlaceholder(
1471       ElemKind::FloatTy, {languageSize, embeddingSize}, "embedding", false);
1472   fillStableRandomData(bindings.allocate(emb)->getHandle(), 4565, 1);
1473 
1474   auto *input = F->getParent()->createPlaceholder(
1475       ElemKind::Int64ITy, {batchSize, sequenceSize}, "input", false);
1476   fillStableRandomIndex(bindings.allocate(input)->getHandle<int64_t>(), 7227,
1477                         10);
1478 
1479   auto *hiddenInit = F->getParent()->createPlaceholder(
1480       ElemKind::FloatTy, {batchSize, embeddingSize}, "hiddenInit", false);
1481   bindings.allocate(hiddenInit)->zero();
1482   Node *hidden = hiddenInit;
1483 
1484   for (unsigned step = 0; step < sequenceSize; step++) {
1485     // STEP2 - Gather a single set of embeddings for the GRU
1486     Node *inputEmbedded = F->createGather("gru.embedding", emb, input);
1487     Node *inputSlice =
1488         F->createSlice("gru.inputSlice", inputEmbedded, {0, step, 0},
1489                        {batchSize, step + 1, embeddingSize});
1490     Node *reshape =
1491         F->createReshape("gru.reshape", inputSlice, {batchSize, embeddingSize});
1492 
1493     // STEP3 - Generate a GRU
1494     // reference implementation:
1495     // https://github.com/pytorch/pytorch/blob/dd5c195646b941d3e20a72847ac48c41e272b8b2/torch/nn/_functions/rnn.py#L46
1496     // similar to /examples/fr2en.cpp
1497 
1498     auto *FCi =
1499         F->createFullyConnected(bindings, "gru.fci", reshape, hiddenSize);
1500     auto *biasI = cast<Placeholder>(FCi->getBias());
1501     auto *filterI = cast<Placeholder>(FCi->getWeights());
1502     fillStableRandomData(bindings.get(biasI)->getHandle(), 8877, 1);
1503     fillStableRandomData(bindings.get(filterI)->getHandle(), 1441, 1);
1504 
1505     auto *FCh =
1506         F->createFullyConnected(bindings, "gru.fch", hidden, hiddenSize);
1507     auto *biasH = cast<Placeholder>(FCh->getBias());
1508     auto *filterH = cast<Placeholder>(FCh->getWeights());
1509     fillStableRandomData(bindings.get(biasH)->getHandle(), 9009, 1);
1510     fillStableRandomData(bindings.get(filterH)->getHandle(), 1001, 1);
1511 
1512     Node *i_r =
1513         F->createSlice("gru.i_r", FCi, {0, 0}, {batchSize, embeddingSize});
1514     Node *i_i = F->createSlice("gru.i_i", FCi, {0, embeddingSize},
1515                                {batchSize, 2 * embeddingSize});
1516     Node *i_n = F->createSlice("gru.i_n", FCi, {0, 2 * embeddingSize},
1517                                {batchSize, 3 * embeddingSize});
1518 
1519     Node *h_r =
1520         F->createSlice("gru.h_r", FCh, {0, 0}, {batchSize, embeddingSize});
1521     Node *h_i = F->createSlice("gru.h_i", FCh, {0, embeddingSize},
1522                                {batchSize, 2 * embeddingSize});
1523     Node *h_n = F->createSlice("gru.h_n", FCh, {0, 2 * embeddingSize},
1524                                {batchSize, 3 * embeddingSize});
1525 
1526     Node *resetgate = F->createSigmoid("gru.resetgate",
1527                                        F->createAdd("i_r_plus_h_r", i_r, h_r));
1528     Node *inputgate = F->createSigmoid("gru.inputgate",
1529                                        F->createAdd("i_i_plus_h_i", i_i, h_i));
1530     Node *newgate = F->createTanh(
1531         "gru.newgate",
1532         F->createAdd("i_n_plus_rg_mult_h_n", i_n,
1533                      F->createMul("rg_mult_h_n", resetgate, h_n)));
1534     hidden = F->createAdd(
1535         "gru.newhidden", newgate,
1536         F->createMul("ig_mult_hmng", inputgate,
1537                      F->createSub("hidden_minus_newgate", hidden, newgate)));
1538   }
1539   // No-op TopK selection to test quantization
1540   Node *downsample = F->createTopK("gru.downsample", hidden, embeddingSize / 2);
1541 
1542   auto *save = F->createSave("save", {downsample, 0});
1543   bindings.allocate(save->getPlaceholder());
1544   return F;
1545 }
1546 
TEST_P(Operator,end2endGRU)1547 TEST_P(Operator, end2endGRU) {
1548   // STEP1 - Generate the first network to record the quantization parameters.
1549   auto *mod = &profileEE.getModule();
1550   auto *modBackend = &backendSpecificEE.getModule();
1551   PlaceholderBindings bindings;
1552   PlaceholderBindings bindingsBackend;
1553   createGRUForQuantization(mod, bindings, "main");
1554   createGRUForQuantization(modBackend, bindingsBackend, "main");
1555 
1556   LoweredInfoMap loweredMapForProf;
1557   CompilationContext cctxProf{&bindings, &loweredMapForProf};
1558   cctxProf.precisionConfig.quantMode = QuantizationMode::Profile;
1559   profileEE.compile(cctxProf);
1560 
1561   // Run graph to capture profile.
1562   profileEE.run(bindings);
1563 
1564   LoweredInfoMap loweredMapForQuant;
1565   CompilationContext cctxQuant{&bindings, &loweredMapForQuant};
1566   cctxQuant.precisionConfig.quantMode = QuantizationMode::Quantize;
1567   PrecisionConfiguration &precConfig = cctxQuant.precisionConfig;
1568   precConfig.quantConfig.infos = quantization::generateNodeProfilingInfos(
1569       bindings, mod->getFunctions().front(), loweredMapForProf);
1570 
1571   // The OpenCL backend does not support some of the nodes in the test;
1572   // explicitly whitelist them here as staying in float, so that the quantizer
1573   // does not complain.
1574   KindSet doNotQuantizeKinds;
1575   if (backendSpecificEE.getBackendName() == "OpenCL") {
1576     precConfig.precisionModeKindSet.insert(Kinded::Kind::TanhNodeKind);
1577     precConfig.precisionModeKindSet.insert(Kinded::Kind::SigmoidNodeKind);
1578     precConfig.precisionModeKindSet.insert(Kinded::Kind::GatherNodeKind);
1579   }
1580 
1581   // STEP2 - Use the profile to quantize a network.
1582 
1583   backendSpecificEE.compile(cctxQuant);
1584   backendSpecificEE.run(bindingsBackend);
1585 
1586   // STEP3 - Compare the results of the original and quantized functions.
1587   auto result1Handle =
1588       bindings.get(bindings.getPlaceholderByNameSlow("save"))->getHandle();
1589   auto result2Handle =
1590       bindingsBackend.get(bindingsBackend.getPlaceholderByNameSlow("save"))
1591           ->getHandle();
1592 
1593   EXPECT_EQ(result1Handle.size(), result2Handle.size());
1594 
1595   for (int i = 0, e = result1Handle.size(); i < e; ++i) {
1596     float mx = result2Handle.raw(result2Handle.minMaxArg().second);
1597     double diff = std::fabs(result2Handle.raw(i) - result1Handle.raw(i)) / mx;
1598 
1599     // Allow 3% difference.
1600     EXPECT_NEAR(diff, 0, 0.03);
1601   }
1602 }
1603 
TEST(Quantization,rescaleSameType)1604 TEST(Quantization, rescaleSameType) {
1605   ExecutionEngine EE{};
1606   PlaceholderBindings bindings;
1607   auto &mod = EE.getModule();
1608   auto *F = mod.createFunction("foo");
1609   auto *input =
1610       mod.createPlaceholder(ElemKind::Int8QTy, {1, 1}, 0.5, 11, "input", true);
1611   bindings.allocate(input)->init(Tensor::InitKind::Broadcast, 21,
1612                                  mod.getPRNG());
1613 
1614   auto *Q = F->createRescaleQuantized(
1615       "rescale", input, mod.uniqueType(ElemKind::Int8QTy, {1, 1}, 0.5, 11));
1616   auto *D = F->createDequantize("dequantize", Q, ElemKind::FloatTy);
1617   auto *save = F->createSave("ret", D);
1618   auto *result = bindings.allocate(save->getPlaceholder());
1619 
1620   EXPECT_EQ(F->getNodes().size(), 3);
1621   EE.compile(CompilationMode::Infer);
1622 
1623   EE.run(bindings);
1624   F = EE.getModule().getFunctions().front();
1625   EXPECT_EQ(F->getNodes().size(), 2);
1626 
1627   auto RH = result->getHandle();
1628   EXPECT_NEAR(RH.at({0, 0}), 5.0, 0.001);
1629 }
1630 
TEST(Quantization,optimizeRescaleQuantize)1631 TEST(Quantization, optimizeRescaleQuantize) {
1632   ExecutionEngine EE{};
1633   PlaceholderBindings bindings;
1634   auto &mod = EE.getModule();
1635   auto *F = mod.createFunction("foo");
1636   auto *input = mod.createPlaceholder(ElemKind::FloatTy, {1, 1}, "input", true);
1637   bindings.allocate(input)->init(Tensor::InitKind::Broadcast, 21,
1638                                  mod.getPRNG());
1639 
1640   auto *Q = F->createQuantize(
1641       "quant", input, mod.uniqueType(ElemKind::Int8QTy, {1, 1}, 0.25, 4));
1642   auto *RS = F->createRescaleQuantized(
1643       "rescale", Q, mod.uniqueType(ElemKind::Int8QTy, {1, 1}, 0.5, 11));
1644   auto *D = F->createDequantize("dequantize", RS, ElemKind::FloatTy);
1645   auto *save = F->createSave("ret", D);
1646   auto *result = bindings.allocate(save->getPlaceholder());
1647 
1648   EXPECT_EQ(F->getNodes().size(), 4);
1649   EE.compile(CompilationMode::Infer);
1650 
1651   EE.run(bindings);
1652 
1653   EXPECT_EQ(EE.getModule().getFunctions().front()->getNodes().size(), 1);
1654 
1655   auto RH = result->getHandle();
1656   EXPECT_NEAR(RH.at({0, 0}), 21.0, 0.001);
1657 }
1658 
1659 /// Check that our asymmetric quantization schema produces
1660 /// the expected scales and offsets for various ranges.
TEST(Quantization,chooseQuantizationAsymmetric)1661 TEST(Quantization, chooseQuantizationAsymmetric) {
1662   // Map float [0.0; 6.0] to int [-128; 127].
1663   TensorQuantizationParams asymmetricParams =
1664       chooseQuantizationParams({0.0, 6.0}, quantization::Schema::Asymmetric);
1665   // Dequantization formula is scale(X - offset).
1666   // So
1667   // 1. scale(-128 - offset) == 0.0
1668   // 2. scale(127 - offset) == 6.0
1669   // Given scale != 0, #1 gives -128 == offset
1670   // Then #2, gives scale == 6.0 / (127 - (-128)).
1671   EXPECT_EQ(asymmetricParams.offset, -128);
1672   EXPECT_NEAR(asymmetricParams.scale, 6.0 / 255, 0.001);
1673 
1674   // Map float [-3.0; 3.0] to int [-128; 127].
1675   asymmetricParams =
1676       chooseQuantizationParams({-3.0, 3.0}, quantization::Schema::Asymmetric);
1677   // Dequantization formula is scale(X - offset).
1678   // So in theory, we should get
1679   // 1. scale(-128 - offset) == -3.0
1680   // 2. scale(127 - offset) == 3.0
1681   // Given scale != 0, #1 + #2 gives scale(-128 + 127 - 2*offset) == 0.0
1682   // offset == -1 / -2 == 0.5
1683   // Then #2 or #1, gives scale == 3.0 / 127.5.
1684   // However, when we get symmetric ranges (i.e., [-X; X]),
1685   // we actually force the zero point to map to 0.
1686   // In other words, scale(0 - offset) == 0.0, so our offset is 0.
1687   // Then our scale is simply: (inputMax - inputMin) / (outputMax - outputMin).
1688   // (3.0 - (-3.0)) / (127 - (-128)) == 6.0 / 255.
1689   EXPECT_EQ(asymmetricParams.offset, 0);
1690   EXPECT_NEAR(asymmetricParams.scale, 6.0 / 255, 0.001);
1691 
1692   // Map float [-2.0; 5.0] to int [-128; 127].
1693   asymmetricParams =
1694       chooseQuantizationParams({-2.0, 5.0}, quantization::Schema::Asymmetric);
1695   // Scale: (5.0 - (-2.0)) / (127 - (-128)) == 7.0 / 255.0
1696   // Offset from min: scale(-128 - offset) == -2.0
1697   //                  7.0 / 255.0 * (-128 - offset) == -2.0
1698   //                  -128 - offset == -2.0 * 255.0 / 7.0
1699   //                  offset == 2.0 * 255.0 / 7.0 - 128
1700   //                  offset == ~-55
1701   EXPECT_EQ(asymmetricParams.offset, (int32_t)(2.0 * 255 / 7.0 - 128));
1702   EXPECT_NEAR(asymmetricParams.scale, 7.0 / 255, 0.001);
1703 
1704   // Map float [2.0; 5.0] to int [-128; 127].
1705   // Make sure we extend the range to include 0.0, i.e.,
1706   // we really map [0.0; 5.0] to int [-128; 127].
1707   asymmetricParams =
1708       chooseQuantizationParams({2.0, 5.0}, quantization::Schema::Asymmetric);
1709   // Scale: (5.0 - (0.0)) / (127 - (-128)) == 5.0 / 255.0
1710   // Offset from min: scale(-128 - offset) == 0.0
1711   EXPECT_EQ(asymmetricParams.offset, -128);
1712   EXPECT_NEAR(asymmetricParams.scale, 5.0 / 255, 0.001);
1713 
1714   // Map float [-8.0; -2.0] to int [-128; 127].
1715   // Make sure we extend the range to include 0.0, i.e.,
1716   // we really map [-8.0; 0.0] to int [-128; 127].
1717   asymmetricParams =
1718       chooseQuantizationParams({-8.0, -2.0}, quantization::Schema::Asymmetric);
1719   // Scale: (0.0 - (-8.0)) / (127 - (-128)) == 8.0 / 255.0
1720   // Offset from min: scale(127 - offset) == 0.0
1721   EXPECT_EQ(asymmetricParams.offset, 127);
1722   EXPECT_NEAR(asymmetricParams.scale, 8.0 / 255, 0.001);
1723 }
1724 
1725 /// Check that our symmetric quantization schema produces
1726 /// the expected scales and offsets for various ranges.
TEST(Quantization,chooseQuantizationSymmetric)1727 TEST(Quantization, chooseQuantizationSymmetric) {
1728   // Map float [0.0; 6.0] to int [-128; 127].
1729   // With symmetric mapping, we basically map [-6.0; 6.0]
1730   TensorQuantizationParams symmetricParams =
1731       chooseQuantizationParams({0.0, 6.0}, quantization::Schema::Symmetric);
1732   // With symmetric mapping offset should always be zero.
1733   EXPECT_EQ(symmetricParams.offset, 0);
1734   EXPECT_NEAR(symmetricParams.scale, 12.0 / 255, 0.001);
1735 
1736   // Map float [-3.0; 3.0] to int [-128; 127].
1737   symmetricParams =
1738       chooseQuantizationParams({-3.0, 3.0}, quantization::Schema::Symmetric);
1739   EXPECT_EQ(symmetricParams.offset, 0);
1740   EXPECT_NEAR(symmetricParams.scale, 6.0 / 255, 0.001);
1741 
1742   // Map float [-2.0; 5.0] to int [-128; 127].
1743   // => [-5.0; 5.0] range for symmetric mode.
1744   symmetricParams =
1745       chooseQuantizationParams({-2.0, 5.0}, quantization::Schema::Symmetric);
1746   EXPECT_EQ(symmetricParams.offset, 0);
1747   EXPECT_NEAR(symmetricParams.scale, 10.0 / 255, 0.001);
1748 
1749   // Map float [2.0; 5.0] to int [-128; 127].
1750   // Ranges are extended to include 0.
1751   // => [0.0; 5.0] range for symmetric mode.
1752   symmetricParams =
1753       chooseQuantizationParams({2.0, 5.0}, quantization::Schema::Symmetric);
1754   // Scale: (5.0 - (0.0)) / (127 - (-128)) == 5.0 / 255.0
1755   // Offset from min: scale(-128 - offset) == 0.0
1756   EXPECT_EQ(symmetricParams.offset, 0);
1757   EXPECT_NEAR(symmetricParams.scale, 10.0 / 255, 0.001);
1758 
1759   // Map float [-8.0; -2.0] to int [-128; 127].
1760   // => [-8.0; 8.0] range for symmetric mode.
1761   symmetricParams =
1762       chooseQuantizationParams({-8.0, -2.0}, quantization::Schema::Symmetric);
1763   EXPECT_EQ(symmetricParams.offset, 0);
1764   EXPECT_NEAR(symmetricParams.scale, 16.0 / 255, 0.001);
1765 }
1766 
1767 /// Check quantization symmetry in presence of infinities.
TEST(Quantization,chooseQuantizationSymmetricInf)1768 TEST(Quantization, chooseQuantizationSymmetricInf) {
1769   auto sym = quantization::Schema::Symmetric;
1770   EXPECT_EQ(chooseQuantizationParams({-INFINITY, INFINITY}, sym).offset, 0);
1771   EXPECT_EQ(chooseQuantizationParams({INFINITY, INFINITY}, sym).offset, 0);
1772   EXPECT_EQ(chooseQuantizationParams({-INFINITY, -INFINITY}, sym).offset, 0);
1773   EXPECT_EQ(chooseQuantizationParams({-INFINITY, 1.0f}, sym).offset, 0);
1774   EXPECT_EQ(chooseQuantizationParams({-INFINITY, -1.0f}, sym).offset, 0);
1775   EXPECT_EQ(chooseQuantizationParams({-1.0f, INFINITY}, sym).offset, 0);
1776   EXPECT_EQ(chooseQuantizationParams({1.0f, INFINITY}, sym).offset, 0);
1777 }
1778 
1779 /// Check that Relu can use our symmetric quantization schema.
TEST(Quantization,reluCanUseSymmetricSchema)1780 TEST(Quantization, reluCanUseSymmetricSchema) {
1781   PlaceholderBindings bindings;
1782   ExecutionEngine EE{};
1783   auto &mod = EE.getModule();
1784   Function *F = mod.createFunction("main");
1785 
1786   Placeholder *input =
1787       mod.createPlaceholder(ElemKind::FloatTy, {10}, "input", false);
1788   auto *inputTensor = bindings.allocate(input);
1789   auto IH = inputTensor->getHandle<float>();
1790   for (dim_t i = 0; i < 10; i++) {
1791     IH.at({i}) = (i % 2 == 0) ? 5 : -5;
1792   }
1793 
1794   // Create symmetric params that will be used for Relu.
1795   TensorQuantizationParams reluParams =
1796       chooseQuantizationParams({0.0, 10.0}, quantization::Schema::Symmetric);
1797   TypeRef reluTy = mod.uniqueType(ElemKind::Int8QTy, {10}, reluParams.scale,
1798                                   reluParams.offset);
1799   TensorQuantizationParams inputParams =
1800       chooseQuantizationParams({-10.0, 10.0}, quantization::Schema::Symmetric);
1801 
1802   QuantizeNode *QN =
1803       F->createQuantize("quant", input,
1804                         mod.uniqueType(ElemKind::Int8QTy, {10},
1805                                        inputParams.scale, inputParams.offset));
1806   ReluNode *RN = F->createRELU("relu", QN, reluTy);
1807   DequantizeNode *DN = F->createDequantize("dequantize", RN, ElemKind::FloatTy);
1808   SaveNode *SN = F->createSave("save", DN);
1809   auto *res = bindings.allocate(SN->getPlaceholder());
1810 
1811   EE.compile(CompilationMode::Infer);
1812   EE.run(bindings);
1813 
1814   // Verify all negative values were correctly set to zero.
1815   auto RH = res->getHandle();
1816   for (dim_t i = 0; i < 10; i++) {
1817     if (i % 2 == 0) {
1818       EXPECT_NEAR(RH.at({i}), 5, 0.05);
1819     } else {
1820       EXPECT_EQ(RH.at({i}), 0);
1821     }
1822   }
1823 }
1824 
1825 /// Check that our symmetric with uint8 quantization schema produces
1826 /// the expected scales and offsets for various ranges.
TEST(Quantization,chooseQuantizationSymmetricWithUInt8)1827 TEST(Quantization, chooseQuantizationSymmetricWithUInt8) {
1828   // Map float [0.0; 6.0] to int [-128; 127].
1829   // With symmetric with uint8 mapping, we basically map [0.0; 6.0]
1830   TensorQuantizationParams symmetricParams = chooseQuantizationParams(
1831       {0.0, 6.0}, quantization::Schema::SymmetricWithUnsigned);
1832   // Given this is a purely positive range, we should use uint8,
1833   // thus int8 - (-128).
1834   EXPECT_EQ(symmetricParams.offset, -128);
1835   EXPECT_NEAR(symmetricParams.scale, 6.0 / 255, 0.001);
1836 
1837   // Map float [-3.0; 3.0] to int [-128; 127].
1838   symmetricParams = chooseQuantizationParams(
1839       {-3.0, 3.0}, quantization::Schema::SymmetricWithUnsigned);
1840   EXPECT_EQ(symmetricParams.offset, 0);
1841   EXPECT_NEAR(symmetricParams.scale, 6.0 / 255, 0.001);
1842 
1843   // Map float [-2.0; 5.0] to int [-128; 127].
1844   // This has negative value, thus we fall back to purely symmetric.
1845   // => [-5.0; 5.0] range for symmetric mode.
1846   symmetricParams = chooseQuantizationParams(
1847       {-2.0, 5.0}, quantization::Schema::SymmetricWithUnsigned);
1848   EXPECT_EQ(symmetricParams.offset, 0);
1849   EXPECT_NEAR(symmetricParams.scale, 10.0 / 255, 0.001);
1850 
1851   // Map float [0; 0] to int [-128; 127].
1852   symmetricParams = chooseQuantizationParams(
1853       {0.0, 0.0}, quantization::Schema::SymmetricWithUnsigned);
1854   EXPECT_EQ(symmetricParams.offset, 0);
1855   EXPECT_NEAR(symmetricParams.scale, 0.1, 0.001);
1856 
1857   // Map float [2.0; 5.0] to int [-128; 127].
1858   // All positive, using uint8.
1859   // However, our quantization schemas always include zero.
1860   // => [0.0; 5.0] range for uint8 mode.
1861   symmetricParams = chooseQuantizationParams(
1862       {2.0, 5.0}, quantization::Schema::SymmetricWithUnsigned);
1863   // Scale: (5.0 - (0.0)) / (127 - (-128)) == 5.0 / 255.0
1864   // Offset from min: scale(-128 - offset) == 0.0
1865   EXPECT_EQ(symmetricParams.offset, -128);
1866   EXPECT_NEAR(symmetricParams.scale, 5.0 / 255, 0.001);
1867 
1868   // Map float [-8.0; -2.0] to int [-128; 127].
1869   // => [-8.0; 8.0] range for symmetric mode.
1870   symmetricParams = chooseQuantizationParams(
1871       {-8.0, -2.0}, quantization::Schema::SymmetricWithUnsigned);
1872   EXPECT_EQ(symmetricParams.offset, 0);
1873   EXPECT_NEAR(symmetricParams.scale, 16.0 / 255, 0.001);
1874 }
1875 
1876 /// Verify the SymmetricWithPower2Scale quantization schema.
chooseQuantParamsPower2Scale(float min,float max,ElemKind qTy)1877 static void chooseQuantParamsPower2Scale(float min, float max, ElemKind qTy) {
1878   auto quantParams = quantization::chooseQuantizationParams(
1879       {min, max}, quantization::Schema::SymmetricWithPower2Scale, qTy);
1880   EXPECT_EQ(quantParams.offset, 0);
1881   EXPECT_TRUE(quantization::isFloatPowerOf2(quantParams.scale));
1882 }
1883 
TEST(Quantization,chooseQuantizationSymmetricWithPower2Scale)1884 TEST(Quantization, chooseQuantizationSymmetricWithPower2Scale) {
1885   chooseQuantParamsPower2Scale(-3.0, 6.0, ElemKind::Int8QTy);
1886   chooseQuantParamsPower2Scale(3.0, 6.0, ElemKind::Int16QTy);
1887   chooseQuantParamsPower2Scale(-6.0, 0.0, ElemKind::Int32QTy);
1888 }
1889 
1890 /// Check that LRN and Softmax are quantized.
TEST(Quantization,quantizeSoftmaxAndLRN)1891 TEST(Quantization, quantizeSoftmaxAndLRN) {
1892   ExecutionEngine EE{};
1893   PlaceholderBindings bindings;
1894   std::unique_ptr<Backend> backend(new MockQuantBackend);
1895   EE.setBackendName("Interpreter");
1896 
1897   auto &mod = EE.getModule();
1898   Function *F = mod.createFunction("main");
1899 
1900   auto *input =
1901       mod.createPlaceholder(ElemKind::FloatTy, {1, 10}, "input", true);
1902   auto *selected =
1903       mod.createPlaceholder(ElemKind::Int64ITy, {1, 10}, "selected", true);
1904   auto *LRN =
1905       F->createLocalResponseNormalization("LRN", input, 2, 1.0, 0.0001, 0.75);
1906   auto *SM = F->createSoftMax("softmax", LRN, selected);
1907   auto *SN = F->createSave("ret", SM);
1908 
1909   quantization::QuantizationConfiguration quantConfig{
1910       {{input->getOutput().generateNodeOutputName(), {0.2f, 2.0f}},
1911        {LRN->getResult().generateNodeOutputName(LRN->getName()), {0.3f, 3.0f}},
1912        {SM->getResult().generateNodeOutputName(SM->getName()), {0.4f, 4.0f}},
1913        {NodeValue::generateNodeOutputName(SN->getName()), {0.4f, 4.0f}}}};
1914 
1915   quantConfig.assertAllNodesQuantized = true;
1916   quantization::quantizeFunction(F, quantConfig, *backend);
1917 
1918   auto qLRNIt = std::find_if(
1919       F->getNodes().begin(), F->getNodes().end(), [](const Node &node) -> bool {
1920         return llvm::isa<LocalResponseNormalizationNode>(&node) &&
1921                node.getNthResult(LocalResponseNormalizationNode::ResultIdx)
1922                    .getType()
1923                    ->isQuantizedType();
1924       });
1925   ASSERT_NE(qLRNIt, F->getNodes().end());
1926   auto qSMIt = std::find_if(F->getNodes().begin(), F->getNodes().end(),
1927                             [](const Node &node) -> bool {
1928                               return llvm::isa<SoftMaxNode>(&node) &&
1929                                      node.getNthResult(SoftMaxNode::ResultIdx)
1930                                          .getType()
1931                                          ->isQuantizedType();
1932                             });
1933   ASSERT_NE(qSMIt, F->getNodes().end());
1934 
1935   // Make sure that SaveNode is not quantized.
1936   for (const auto &node : F->getNodes()) {
1937     if (auto *saveNode = llvm::dyn_cast<SaveNode>(&node)) {
1938       EXPECT_FALSE(saveNode->getInput().getType()->isQuantizedType());
1939     }
1940   }
1941 }
1942 
1943 /// Check that Select is quantized.
TEST(Quantization,quantizeSelect)1944 TEST(Quantization, quantizeSelect) {
1945   ExecutionEngine EE{};
1946   PlaceholderBindings bindings;
1947   std::unique_ptr<Backend> backend(new MockQuantBackend);
1948   EE.setBackendName("Interpreter");
1949 
1950   auto &mod = EE.getModule();
1951   Function *F = mod.createFunction("main");
1952 
1953   auto *LHS = mod.createPlaceholder(ElemKind::FloatTy, {1, 10}, "LHS", false);
1954   auto *RHS = mod.createPlaceholder(ElemKind::FloatTy, {1, 10}, "RHS", false);
1955   auto *cond = mod.createPlaceholder(ElemKind::BoolTy, {1, 10}, "cond", false);
1956   auto *select = F->createSelect("select", cond, LHS, RHS);
1957   F->createSave("save", select);
1958 
1959   TensorProfilingParams LHSPP = {0.0, 1.0};
1960   TensorProfilingParams RHSPP = {-1.3, 2.7};
1961   TensorProfilingParams selectPP = {-2, 3.1};
1962 
1963   quantization::QuantizationConfiguration quantConfig{
1964       {{LHS->getOutput().generateNodeOutputName(), LHSPP},
1965        {RHS->getOutput().generateNodeOutputName(), RHSPP},
1966        {select->getResult().generateNodeOutputName(), selectPP}}};
1967 
1968   quantConfig.assertAllNodesQuantized = true;
1969   quantization::quantizeFunction(F, quantConfig, *backend);
1970 
1971   // Get quantization parameters for verification.
1972   TensorQuantizationParams LHSQP = chooseQuantizationParams(
1973       LHSPP, quantConfig.schema, quantConfig.precision);
1974   TensorQuantizationParams RHSQP = chooseQuantizationParams(
1975       RHSPP, quantConfig.schema, quantConfig.precision);
1976   TensorQuantizationParams selectQP = chooseQuantizationParams(
1977       selectPP, quantConfig.schema, quantConfig.precision);
1978 
1979   auto it = std::find_if(
1980       F->getNodes().begin(), F->getNodes().end(),
1981       [](const Node &node) -> bool { return llvm::isa<SelectNode>(&node); });
1982   ASSERT_NE(it, F->getNodes().end());
1983 
1984   SelectNode *qSelect = llvm::cast<SelectNode>(&(*it));
1985   TypeRef qSelectTy = qSelect->getResult().getType();
1986   TypeRef qLHSTy = qSelect->getLHS().getType();
1987   TypeRef qRHSTy = qSelect->getRHS().getType();
1988 
1989   ASSERT_TRUE(qSelectTy->isQuantizedType());
1990   EXPECT_EQ(qSelectTy->getScale(), selectQP.scale);
1991   EXPECT_EQ(qSelectTy->getOffset(), selectQP.offset);
1992   EXPECT_EQ(qLHSTy->getScale(), LHSQP.scale);
1993   EXPECT_EQ(qLHSTy->getOffset(), LHSQP.offset);
1994   EXPECT_EQ(qRHSTy->getScale(), RHSQP.scale);
1995   EXPECT_EQ(qRHSTy->getOffset(), RHSQP.offset);
1996 }
1997 
1998 /// Check that AvgPool is quantized, and its input and output have different
1999 /// scale and offset.
TEST(Quantization,quantizeAvgPool)2000 TEST(Quantization, quantizeAvgPool) {
2001   ExecutionEngine EE{};
2002   PlaceholderBindings bindings;
2003   std::unique_ptr<Backend> backend(new MockQuantBackend);
2004   EE.setBackendName("Interpreter");
2005 
2006   auto &mod = EE.getModule();
2007   Function *F = mod.createFunction("main");
2008 
2009   auto *input =
2010       mod.createPlaceholder(ElemKind::FloatTy, {1, 3, 3, 1}, "input", true);
2011   auto *pool = F->createAvgPool("pool", input, {2, 2}, {1, 1}, {0, 0, 0, 0});
2012   auto *s = F->createSave("save", pool);
2013 
2014   quantization::QuantizationConfiguration quantConfig{{
2015       {input->getOutput().generateNodeOutputName(), {-2.0f, 2.0f}},
2016       {pool->getResult().generateNodeOutputName(), {0.3f, 3.0f}},
2017       {NodeValue::generateNodeOutputName(s->getName()), {0.4f, 4.0f}},
2018   }};
2019 
2020   quantConfig.assertAllNodesQuantized = true;
2021   quantization::quantizeFunction(F, quantConfig, *backend);
2022 
2023   auto qPool = std::find_if(F->getNodes().begin(), F->getNodes().end(),
2024                             [](const Node &node) -> bool {
2025                               return llvm::isa<AvgPoolNode>(&node) &&
2026                                      node.getNthResult(AvgPoolNode::ResultIdx)
2027                                          .getType()
2028                                          ->isQuantizedType();
2029                             });
2030   ASSERT_NE(qPool, F->getNodes().end());
2031   auto *avgPool = llvm::cast<AvgPoolNode>(qPool);
2032   ASSERT_NE(avgPool->getInput().getType()->getScale(),
2033             avgPool->getResult().getType()->getScale());
2034   ASSERT_NE(avgPool->getInput().getType()->getOffset(),
2035             avgPool->getResult().getType()->getOffset());
2036 }
2037 
2038 /// Test option to disable quantization of specific node kinds in the graph.
TEST(Quantization,quantizeGraphPartially)2039 TEST(Quantization, quantizeGraphPartially) {
2040   ExecutionEngine EE{};
2041   PlaceholderBindings bindings;
2042   auto &mod = EE.getModule();
2043   Function *F = mod.createFunction("main");
2044 
2045   auto *LHS = mod.createPlaceholder(ElemKind::FloatTy, {3, 3}, "lhs", true);
2046   auto *RHS = mod.createPlaceholder(ElemKind::FloatTy, {3, 3}, "rhs", true);
2047   bindings.allocate(LHS)->init(Tensor::InitKind::Xavier, 3, mod.getPRNG());
2048   bindings.allocate(RHS)->init(Tensor::InitKind::Xavier, 3, mod.getPRNG());
2049 
2050   auto *MMN = F->createMatMul("matmul", LHS, RHS);
2051   auto *TN = F->createTanh("tanh", MMN);
2052   auto *save = F->createSave("ret", TN);
2053   auto *result = save->getPlaceholder();
2054   bindings.allocate(result);
2055 
2056   // Note that we are creating profiling info even for nodes that will not be
2057   // quantized. This is how we expect quantizeFunction() to behave, as
2058   // quantization profiling will still get a profile for these nodes.
2059   quantization::QuantizationConfiguration quantConfig{{
2060       {LHS->getOutput().generateNodeOutputName(), {0.3f, 3.0f}},
2061       {RHS->getOutput().generateNodeOutputName(), {0.4f, 4.0f}},
2062       {MMN->getResult().generateNodeOutputName(), {0.6f, 6.0f}},
2063       {TN->getResult().generateNodeOutputName(), {0.5f, 5.0f}},
2064   }};
2065 
2066   // Do not quantize any tanh nodes.
2067   KindSet doNotQuantizeKinds;
2068   doNotQuantizeKinds.insert(Kinded::Kind::TanhNodeKind);
2069 
2070   quantConfig.assertAllNodesQuantized = true;
2071   std::unique_ptr<Backend> backend(createBackend(EE.getBackendName()));
2072   quantization::quantizeFunction(F, quantConfig, *backend,
2073                                  /* loweredMap */ {}, doNotQuantizeKinds);
2074 
2075   // Make sure that graph can be compiled and run.
2076   ::glow::convertPlaceholdersToConstants(F, bindings, {result});
2077 
2078   CompilationContext cctx;
2079   cctx.compMode = CompilationMode::Infer;
2080   // Do not perform any compile-time constant folding.
2081   cctx.optimizationOpts.enableConstantFolding = false;
2082   EE.compile(cctx);
2083 
2084   EE.run(bindings);
2085 
2086   {
2087     // Verify that the output variable is not quantized, and that it has a
2088     // single save node writer, which is also not quantized.
2089     EXPECT_TRUE(!result->getType()->isQuantizedType());
2090     ASSERT_EQ(result->getUsers().size(), 1);
2091     auto *SN = llvm::dyn_cast<SaveNode>(result->getUsers().begin()->getUser());
2092     ASSERT_TRUE(SN);
2093     EXPECT_TRUE(!SN->getOutput().getType()->isQuantizedType());
2094 
2095     // Verify that the tanh is not quantized.
2096     auto *TN = llvm::dyn_cast<TanhNode>(SN->getInput());
2097     ASSERT_TRUE(TN);
2098     EXPECT_TRUE(!TN->getResult().getType()->isQuantizedType());
2099 
2100     // Verify that the input to the tanh is a dequantize node.
2101     auto *DN = llvm::dyn_cast<DequantizeNode>(TN->getInput());
2102     ASSERT_TRUE(DN);
2103 
2104     // Verify that the matmul is quantized.
2105     auto *MMN = llvm::dyn_cast<MatMulNode>(DN->getInput());
2106     ASSERT_TRUE(MMN);
2107     EXPECT_TRUE(MMN->getResult().getType()->isQuantizedType());
2108 
2109     // Verify that the variable inputs to the matmul are quantized.
2110     auto *LHS = llvm::dyn_cast<Constant>(MMN->getLHS());
2111     ASSERT_TRUE(LHS);
2112     EXPECT_TRUE(LHS->getType()->isQuantizedType());
2113 
2114     auto *RHS = llvm::dyn_cast<Constant>(MMN->getRHS());
2115     ASSERT_TRUE(RHS);
2116     EXPECT_TRUE(RHS->getType()->isQuantizedType());
2117   }
2118 }
2119 
2120 /// Test option to disable quantization of specific node kinds in the graph,
2121 /// where there are multiple of that node kind.
TEST(Quantization,quantizeGraphPartiallyMultipleNodes)2122 TEST(Quantization, quantizeGraphPartiallyMultipleNodes) {
2123   ExecutionEngine EE{};
2124   PlaceholderBindings bindings;
2125   auto &mod = EE.getModule();
2126   Function *F = mod.createFunction("main");
2127 
2128   auto *LHS = mod.createPlaceholder(ElemKind::FloatTy, {3, 3}, "lhs", true);
2129   auto *RHS = mod.createPlaceholder(ElemKind::FloatTy, {3, 3}, "rhs", true);
2130   bindings.allocate(LHS)->init(Tensor::InitKind::Xavier, 3, mod.getPRNG());
2131   bindings.allocate(RHS)->init(Tensor::InitKind::Xavier, 3, mod.getPRNG());
2132 
2133   auto *TNLHS = F->createTanh("tanh", LHS);
2134   auto *MMN = F->createMatMul("matmul", TNLHS, RHS);
2135   auto *TN = F->createTanh("tanh", MMN);
2136   auto *save = F->createSave("ret", TN);
2137   auto *result = save->getPlaceholder();
2138   bindings.allocate(result);
2139 
2140   // Note that we are creating profiling info even for nodes that will not be
2141   // quantized. This is how we expect quantizeFunction() to behave, as
2142   // quantization profiling will still get a profile for these nodes.
2143   quantization::QuantizationConfiguration quantConfig{{
2144       {LHS->getOutput().generateNodeOutputName(), {0.3f, 3.0f}},
2145       {TNLHS->getResult().generateNodeOutputName(), {0.4f, 4.0f}},
2146       {RHS->getOutput().generateNodeOutputName(), {0.4f, 4.0f}},
2147       {MMN->getResult().generateNodeOutputName(), {0.6f, 6.0f}},
2148       {TN->getResult().generateNodeOutputName(), {0.5f, 5.0f}},
2149   }};
2150 
2151   // Do not quantize any tanh nodes.
2152   KindSet doNotQuantizeKinds;
2153   doNotQuantizeKinds.insert(Kinded::Kind::TanhNodeKind);
2154 
2155   quantConfig.assertAllNodesQuantized = true;
2156   std::unique_ptr<Backend> backend(createBackend(EE.getBackendName()));
2157   quantization::quantizeFunction(F, quantConfig, *backend,
2158                                  /* loweredMap */ {}, doNotQuantizeKinds);
2159 
2160   // Make sure that graph can be compiled and run.
2161   ::glow::convertPlaceholdersToConstants(F, bindings, {result});
2162 
2163   CompilationContext cctx;
2164   cctx.compMode = CompilationMode::Infer;
2165   // Do not perform any compile-time constant folding.
2166   cctx.optimizationOpts.enableConstantFolding = false;
2167   EE.compile(cctx);
2168 
2169   EE.run(bindings);
2170 
2171   {
2172     // Verify that the output variable is not quantized, and that it has a
2173     // single save node writer, which is also not quantized.
2174     EXPECT_TRUE(!result->getType()->isQuantizedType());
2175     ASSERT_EQ(result->getUsers().size(), 1);
2176     auto *SN = llvm::dyn_cast<SaveNode>(result->getUsers().begin()->getUser());
2177     ASSERT_TRUE(SN);
2178     EXPECT_TRUE(!SN->getOutput().getType()->isQuantizedType());
2179 
2180     // Verify that the tanh is not quantized.
2181     auto *TN1 = llvm::dyn_cast<TanhNode>(SN->getInput());
2182     ASSERT_TRUE(TN1);
2183     EXPECT_TRUE(!TN1->getResult().getType()->isQuantizedType());
2184 
2185     // Verify that the input to the tanh is a dequantize node.
2186     auto *DN = llvm::dyn_cast<DequantizeNode>(TN1->getInput());
2187     ASSERT_TRUE(DN);
2188 
2189     // Verify that the matmul is quantized.
2190     auto *MMN = llvm::dyn_cast<MatMulNode>(DN->getInput());
2191     ASSERT_TRUE(MMN);
2192     EXPECT_TRUE(MMN->getResult().getType()->isQuantizedType());
2193 
2194     // Verify that the LHS input is a quantize node.
2195     auto *QN = llvm::dyn_cast<QuantizeNode>(MMN->getLHS());
2196     ASSERT_TRUE(QN);
2197 
2198     // Verify that the second tanh node is also not quantized.
2199     auto *TN2 = llvm::dyn_cast<TanhNode>(QN->getInput());
2200     ASSERT_TRUE(TN2);
2201     EXPECT_TRUE(!TN2->getResult().getType()->isQuantizedType());
2202 
2203     // Verify that the input variable to the tanh is not quantized.
2204     auto *varTN2 = llvm::dyn_cast<Constant>(TN2->getInput());
2205     ASSERT_TRUE(varTN2);
2206     EXPECT_TRUE(!varTN2->getType()->isQuantizedType());
2207 
2208     // Verify that the RHS input to the matmul is a quantized variable.
2209     auto *RHS = llvm::dyn_cast<Constant>(MMN->getRHS());
2210     ASSERT_TRUE(RHS);
2211     EXPECT_TRUE(RHS->getType()->isQuantizedType());
2212   }
2213 }
2214 
2215 /// Test option to disable quantization of multiple specific node kinds in the
2216 /// graph.
TEST(Quantization,quantizeGraphPartiallyMultipleKinds)2217 TEST(Quantization, quantizeGraphPartiallyMultipleKinds) {
2218   ExecutionEngine EE{};
2219   PlaceholderBindings bindings;
2220   auto &mod = EE.getModule();
2221   Function *F = mod.createFunction("main");
2222 
2223   auto *LHS = mod.createPlaceholder(ElemKind::FloatTy, {3, 3}, "lhs", true);
2224   auto *RHS = mod.createPlaceholder(ElemKind::FloatTy, {3, 3}, "rhs", true);
2225   bindings.allocate(LHS)->init(Tensor::InitKind::Xavier, 3, mod.getPRNG());
2226   bindings.allocate(RHS)->init(Tensor::InitKind::Xavier, 3, mod.getPRNG());
2227 
2228   auto *MMN = F->createMatMul("matmul", LHS, RHS);
2229   auto *CN = F->createAdd("concat", LHS, MMN);
2230   auto *TN = F->createTanh("tanh", CN);
2231   auto *save = F->createSave("ret", TN);
2232   auto *result = save->getPlaceholder();
2233   bindings.allocate(result);
2234 
2235   // Note that we are creating profiling info even for nodes that will not be
2236   // quantized. This is how we expect quantizeFunction() to behave, as
2237   // quantization profiling will still get a profile for these nodes.
2238   quantization::QuantizationConfiguration quantConfig{{
2239       {LHS->getOutput().generateNodeOutputName(), {0.3f, 3.0f}},
2240       {RHS->getOutput().generateNodeOutputName(), {0.4f, 4.0f}},
2241       {MMN->getResult().generateNodeOutputName(), {0.6f, 6.0f}},
2242       {CN->getResult().generateNodeOutputName(), {0.6f, 6.0f}},
2243       {TN->getResult().generateNodeOutputName(), {0.5f, 5.0f}},
2244   }};
2245 
2246   // Do not quantize any tanh or add nodes.
2247   KindSet doNotQuantizeKinds;
2248   doNotQuantizeKinds.insert(Kinded::Kind::TanhNodeKind);
2249   doNotQuantizeKinds.insert(Kinded::Kind::AddNodeKind);
2250 
2251   quantConfig.assertAllNodesQuantized = true;
2252   std::unique_ptr<Backend> backend(createBackend(EE.getBackendName()));
2253   quantization::quantizeFunction(F, quantConfig, *backend,
2254                                  /* loweredMap */ {}, doNotQuantizeKinds);
2255 
2256   // Make sure that graph can be compiled and run.
2257   ::glow::convertPlaceholdersToConstants(F, bindings, {result});
2258 
2259   CompilationContext cctx;
2260   cctx.compMode = CompilationMode::Infer;
2261   // Do not perform any compile-time constant folding.
2262   cctx.optimizationOpts.enableConstantFolding = false;
2263   EE.compile(cctx);
2264 
2265   EE.run(bindings);
2266 
2267   {
2268     // Verify that the output variable is not quantized, and that it has a
2269     // single save node writer, which is also not quantized.
2270     EXPECT_TRUE(!result->getType()->isQuantizedType());
2271     ASSERT_EQ(result->getUsers().size(), 1);
2272     auto *SN = llvm::dyn_cast<SaveNode>(result->getUsers().begin()->getUser());
2273     ASSERT_TRUE(SN);
2274     EXPECT_TRUE(!SN->getOutput().getType()->isQuantizedType());
2275 
2276     // Verify that the tanh is not quantized.
2277     auto *TN = llvm::dyn_cast<TanhNode>(SN->getInput());
2278     ASSERT_TRUE(TN);
2279     EXPECT_TRUE(!TN->getResult().getType()->isQuantizedType());
2280 
2281     // Verify that the input to the tanh is a non-quantized add node.
2282     auto *AN = llvm::dyn_cast<AddNode>(TN->getInput());
2283     ASSERT_TRUE(AN);
2284     EXPECT_TRUE(!TN->getResult().getType()->isQuantizedType());
2285 
2286     // Verify that the LHS input to the AddNode is an unquantized variable.
2287     auto varANLHS = llvm::dyn_cast<Constant>(AN->getLHS());
2288     ASSERT_TRUE(varANLHS);
2289     EXPECT_TRUE(!varANLHS->getType()->isQuantizedType());
2290 
2291     // Verify that the RHS input to the AddNode is a dequantize node.
2292     auto *DN = llvm::dyn_cast<DequantizeNode>(AN->getRHS());
2293     ASSERT_TRUE(DN);
2294 
2295     // Verify that the matmul is quantized.
2296     auto *MMN = llvm::dyn_cast<MatMulNode>(DN->getInput());
2297     ASSERT_TRUE(MMN);
2298     EXPECT_TRUE(MMN->getResult().getType()->isQuantizedType());
2299 
2300     // Verify that the variable inputs to the matmul are quantized.
2301     auto *LHS = llvm::dyn_cast<Constant>(MMN->getLHS());
2302     ASSERT_TRUE(LHS);
2303     EXPECT_TRUE(LHS->getType()->isQuantizedType());
2304 
2305     auto *RHS = llvm::dyn_cast<Constant>(MMN->getRHS());
2306     ASSERT_TRUE(RHS);
2307     EXPECT_TRUE(RHS->getType()->isQuantizedType());
2308   }
2309 }
2310 
2311 /// Check that quantizeFunction directly converts the constants
2312 /// instead of leaving quantize node around.
TEST(Quantization,quantizeFunctionConvertConstant)2313 TEST(Quantization, quantizeFunctionConvertConstant) {
2314   ExecutionEngine EE{};
2315   PlaceholderBindings bindings;
2316   auto &mod = EE.getModule();
2317   Function *F = mod.createFunction("main");
2318 
2319   auto *LHS = mod.createPlaceholder(ElemKind::FloatTy, {3, 3}, "lhs", true);
2320   auto *RHS = mod.createConstant(ElemKind::FloatTy, {3, 3}, "rhs");
2321   bindings.allocate(LHS)->init(Tensor::InitKind::Xavier, 3, mod.getPRNG());
2322   RHS->getPayloadMutable().init(Tensor::InitKind::Xavier, 3, mod.getPRNG());
2323 
2324   auto *MMN = F->createMatMul("matmul", LHS, RHS);
2325   auto *save = F->createSave("ret", MMN);
2326   auto *result = save->getPlaceholder();
2327   bindings.allocate(result);
2328 
2329   // Note that we are creating profiling info even for nodes that will not be
2330   // quantized. This is how we expect quantizeFunction() to behave, as
2331   // quantization profiling will still get a profile for these nodes.
2332   quantization::QuantizationConfiguration quantConfig{{
2333       {LHS->getOutput().generateNodeOutputName(), {0.3f, 3.0f}},
2334       {RHS->getOutput().generateNodeOutputName(), {0.4f, 4.0f}},
2335       {MMN->getResult().generateNodeOutputName(), {0.6f, 6.0f}},
2336   }};
2337 
2338   quantConfig.assertAllNodesQuantized = true;
2339   std::unique_ptr<Backend> backend(createBackend(EE.getBackendName()));
2340   quantization::quantizeFunction(F, quantConfig, *backend);
2341 
2342   optimize(F, CompilationMode::Infer);
2343   CompilationContext cctx;
2344   convertQuantizedConstants(F, cctx);
2345 
2346   {
2347     // Verify that the output variable is not quantized, and that it has a
2348     // single save node writer, which is also not quantized.
2349     EXPECT_TRUE(!result->getType()->isQuantizedType());
2350     ASSERT_EQ(result->getUsers().size(), 1);
2351     auto *SN = llvm::dyn_cast<SaveNode>(result->getUsers().begin()->getUser());
2352     ASSERT_TRUE(SN);
2353     EXPECT_TRUE(!SN->getOutput().getType()->isQuantizedType());
2354 
2355     // Verify that the input to save is a dequantize node.
2356     auto *DN = llvm::dyn_cast<DequantizeNode>(SN->getInput());
2357     ASSERT_TRUE(DN);
2358 
2359     // Verify that the matmul is quantized.
2360     auto *MMN = llvm::dyn_cast<MatMulNode>(DN->getInput());
2361     ASSERT_TRUE(MMN);
2362     EXPECT_TRUE(MMN->getResult().getType()->isQuantizedType());
2363 
2364     // Verify that the variable inputs to the matmul are quantized.
2365     auto *LHSQuantize = llvm::dyn_cast<QuantizeNode>(MMN->getLHS());
2366     ASSERT_TRUE(LHSQuantize);
2367     EXPECT_EQ(LHSQuantize->getInput().getNode(), LHS);
2368 
2369     auto *RHS = llvm::dyn_cast<Constant>(MMN->getRHS());
2370     ASSERT_TRUE(RHS);
2371     EXPECT_TRUE(RHS->getType()->isQuantizedType());
2372   }
2373 
2374   // Make sure that graph can be compiled and run.
2375   EE.compile(CompilationMode::Infer);
2376 
2377   EE.run(bindings);
2378 }
2379 
2380 /// Check that the slice node doesn't change the quantization parameters between
2381 /// its input and output.
TEST(Quantization,quantizeSlice)2382 TEST(Quantization, quantizeSlice) {
2383   ExecutionEngine EE{};
2384   PlaceholderBindings bindings;
2385   auto &mod = EE.getModule();
2386   Function *F = mod.createFunction("main");
2387 
2388   auto *input = mod.createPlaceholder(ElemKind::FloatTy, {4}, "input", true);
2389   bindings.allocate(input)->init(Tensor::InitKind::Xavier, 3, mod.getPRNG());
2390 
2391   auto *slice = F->createSlice("slice", input, {2}, {3});
2392   auto *save = F->createSave("ret", slice);
2393   auto *result = save->getPlaceholder();
2394   bindings.allocate(result);
2395 
2396   quantization::QuantizationConfiguration quantConfig{{
2397       {slice->getResult().generateNodeOutputName(), {0.2f, 2.0f}},
2398       {input->getOutput().generateNodeOutputName(), {0.4f, 4.0f}},
2399   }};
2400 
2401   // Compute quantization parameters for verification.
2402   auto sliceInpTQP = chooseQuantizationParams({0.4, 4.0}, quantConfig.schema,
2403                                               quantConfig.precision);
2404 
2405   quantConfig.assertAllNodesQuantized = true;
2406   std::unique_ptr<Backend> backend(createBackend(EE.getBackendName()));
2407   quantization::quantizeFunction(F, quantConfig, *backend);
2408 
2409   optimize(F, CompilationMode::Infer);
2410 
2411   {
2412     // Verify that the output variable is not quantized, and that it has a
2413     // single save node writer, which is also not quantized.
2414     EXPECT_TRUE(!result->getType()->isQuantizedType());
2415     ASSERT_EQ(result->getUsers().size(), 1);
2416     auto *SN = llvm::dyn_cast<SaveNode>(result->getUsers().begin()->getUser());
2417     ASSERT_TRUE(SN);
2418     EXPECT_TRUE(!SN->getOutput().getType()->isQuantizedType());
2419 
2420     // Verify that the input to save is a dequantize node.
2421     auto *DN = llvm::dyn_cast<DequantizeNode>(SN->getInput());
2422     ASSERT_TRUE(DN);
2423 
2424     // Verify that the slice is rescaled after being quantized.
2425     // The reason we need a rescale is because slicing doesn't perform rescaling
2426     // by itself.
2427     // Note: after optimization, the RescaleQuantized node created for the Slice
2428     // gets merged with the dequantize node.
2429     auto *qslice = llvm::dyn_cast<SliceNode>(DN->getInput());
2430     ASSERT_TRUE(qslice);
2431     ASSERT_TRUE(qslice->getResult().getType()->isQuantizedType());
2432     EXPECT_EQ(qslice->getResult().getType()->getOffset(), sliceInpTQP.offset);
2433     EXPECT_EQ(qslice->getResult().getType()->getScale(), sliceInpTQP.scale);
2434 
2435     // Verify that the variable inputs to the matmul are quantized.
2436     auto *qinput = llvm::dyn_cast<QuantizeNode>(qslice->getInput());
2437     ASSERT_TRUE(qinput);
2438     EXPECT_EQ(qinput->getResult().getType()->getOffset(),
2439               qslice->getResult().getType()->getOffset());
2440     EXPECT_EQ(qinput->getResult().getType()->getScale(),
2441               qslice->getResult().getType()->getScale());
2442     EXPECT_EQ(qinput->getInput().getNode(), input);
2443   }
2444 
2445   // Make sure that graph can be compiled and run.
2446   EE.compile(CompilationMode::Infer);
2447 
2448   EE.run(bindings);
2449 }
2450 
2451 /// Check that the reshape node doesn't change the quantization parameters
2452 /// between its input and output.
TEST(Quantization,quantizeReshape)2453 TEST(Quantization, quantizeReshape) {
2454   ExecutionEngine EE{};
2455   PlaceholderBindings bindings;
2456   auto &mod = EE.getModule();
2457   Function *F = mod.createFunction("main");
2458 
2459   auto *input = mod.createPlaceholder(ElemKind::FloatTy, {3, 3}, "input", true);
2460   bindings.allocate(input)->init(Tensor::InitKind::Xavier, 3, mod.getPRNG());
2461 
2462   auto *reshape = F->createReshape("reshape", input, {9});
2463   auto *save = F->createSave("ret", reshape);
2464   auto *result = save->getPlaceholder();
2465   bindings.allocate(result);
2466 
2467   quantization::QuantizationConfiguration quantConfig{{
2468       {reshape->getResult().generateNodeOutputName(), {0.2f, 2.0f}},
2469       {input->getOutput().generateNodeOutputName(), {0.4f, 4.0f}},
2470   }};
2471 
2472   // Compute quantization parameters for verification.
2473   auto reshapeInpTQP = chooseQuantizationParams({0.4, 4.0}, quantConfig.schema,
2474                                                 quantConfig.precision);
2475 
2476   quantConfig.assertAllNodesQuantized = true;
2477   std::unique_ptr<Backend> backend(createBackend(EE.getBackendName()));
2478   quantization::quantizeFunction(F, quantConfig, *backend);
2479 
2480   optimize(F, CompilationMode::Infer);
2481 
2482   {
2483     // Verify that the output variable is not quantized, and that it has a
2484     // single save node writer, which is also not quantized.
2485     EXPECT_TRUE(!result->getType()->isQuantizedType());
2486     ASSERT_EQ(result->getUsers().size(), 1);
2487     auto *SN = llvm::dyn_cast<SaveNode>(result->getUsers().begin()->getUser());
2488     ASSERT_TRUE(SN);
2489     EXPECT_TRUE(!SN->getOutput().getType()->isQuantizedType());
2490 
2491     // Verify that the input to save is a dequantize node.
2492     auto *DN = llvm::dyn_cast<DequantizeNode>(SN->getInput());
2493     ASSERT_TRUE(DN);
2494 
2495     // Verify that the reshape is rescaled after being quantized.
2496     // The reason we need a rescale is because reshaping doesn't perform
2497     // rescaling by itself.
2498     // Note: after optimization, the RescaleQuantized node created for the
2499     // Reshape gets merged with the dequantize node.
2500     auto *qreshape = llvm::dyn_cast<ReshapeNode>(DN->getInput());
2501     ASSERT_TRUE(qreshape);
2502     ASSERT_TRUE(qreshape->getResult().getType()->isQuantizedType());
2503     EXPECT_EQ(qreshape->getResult().getType()->getOffset(),
2504               reshapeInpTQP.offset);
2505     EXPECT_EQ(qreshape->getResult().getType()->getScale(), reshapeInpTQP.scale);
2506 
2507     // Verify that the variable inputs to the matmul are quantized.
2508     auto *qinput = llvm::dyn_cast<QuantizeNode>(qreshape->getInput());
2509     ASSERT_TRUE(qinput);
2510     EXPECT_EQ(qinput->getResult().getType()->getOffset(),
2511               qreshape->getResult().getType()->getOffset());
2512     EXPECT_EQ(qinput->getResult().getType()->getScale(),
2513               qreshape->getResult().getType()->getScale());
2514     EXPECT_EQ(qinput->getInput().getNode(), input);
2515   }
2516 
2517   // Make sure that graph can be compiled and run.
2518   EE.compile(CompilationMode::Infer);
2519 
2520   EE.run(bindings);
2521 }
2522 
2523 /// Mock backend that does not lower FC nodes.
2524 class MockBackendUnloweredFC : public MockBackend {
shouldLower(const Node * N) const2525   bool shouldLower(const Node *N) const override {
2526     if (N->getKind() == Kinded::Kind::FullyConnectedNodeKind) {
2527       return false;
2528     }
2529     return true;
2530   }
isOpSupported(const NodeInfo & NI) const2531   bool isOpSupported(const NodeInfo &NI) const override { return true; }
2532 };
2533 
2534 /// Mock backend that does lower FC nodes.
2535 class MockBackendLoweredFC : public MockBackend {
shouldLower(const Node * N) const2536   bool shouldLower(const Node *N) const override { return true; }
isOpSupported(const NodeInfo & NI) const2537   bool isOpSupported(const NodeInfo &NI) const override { return true; }
2538 };
2539 
2540 /// Create a simple network with an FC given \p bindings, \p EE, and \p F.
2541 /// \returns the FC node.
createSimpleFCNet(PlaceholderBindings & bindings,ExecutionEngine & EE,Function & F)2542 static FullyConnectedNode *createSimpleFCNet(PlaceholderBindings &bindings,
2543                                              ExecutionEngine &EE, Function &F) {
2544   auto &mod = EE.getModule();
2545   auto *input = mod.createPlaceholder(ElemKind::FloatTy, {1, 3}, "input", true);
2546   auto *W = mod.createPlaceholder(ElemKind::FloatTy, {3, 3}, "weights", true);
2547   auto *B = mod.createPlaceholder(ElemKind::FloatTy, {3}, "bias", true);
2548 
2549   bindings.allocate(input);
2550   bindings.allocate(W)->init(Tensor::InitKind::Xavier, 3, mod.getPRNG());
2551   bindings.allocate(B)->init(Tensor::InitKind::Broadcast, 0.1, mod.getPRNG());
2552 
2553   auto *FC = F.createFullyConnected("FC", input, W, B);
2554   auto *S = F.createSave("ret", FC);
2555   ::glow::convertPlaceholdersToConstants(&F, bindings,
2556                                          {input, S->getPlaceholder()});
2557   bindings.allocate(S->getPlaceholder());
2558 
2559   return FC;
2560 }
2561 
2562 /// Helper to look for a node with kind \p NodeClass in \p F. If found, \returns
2563 /// a pointer to the node. Otherwise \returns a nullptr.
2564 template <class NodeClass>
findNodeKindOrReturnNull(Function * F)2565 static NodeClass *findNodeKindOrReturnNull(Function *F) {
2566   auto it = std::find_if(
2567       F->getNodes().begin(), F->getNodes().end(),
2568       [](const Node &node) -> bool { return llvm::isa<NodeClass>(&node); });
2569   if (it == F->getNodes().end()) {
2570     return nullptr;
2571   }
2572   return &llvm::cast<NodeClass>(*it);
2573 }
2574 
2575 /// Profile and quantize a graph with an FC, and make sure that we find the
2576 /// correct quantization parameters, whether the \p BackendClass does or does
2577 /// not lower the FC given \p expectLoweredFC. Note that in this test we
2578 /// replicate the logic from optimizeFunction(), wherein we lower and then call
2579 /// profileQuantization(), in order to ensure each stage of the compilation
2580 /// pipeline for profiling/quantization is correct.
2581 template <class BackendClass>
testProfileQuantizationOfFC(bool expectLoweredFC,bool rowwiseQuantizeFC)2582 static void testProfileQuantizationOfFC(bool expectLoweredFC,
2583                                         bool rowwiseQuantizeFC) {
2584   ExecutionEngine profileEE{};
2585   Function *profileF = profileEE.getModule().createFunction("profile");
2586   PlaceholderBindings profilebindings;
2587   FullyConnectedNode *FC =
2588       createSimpleFCNet(profilebindings, profileEE, *profileF);
2589   auto outputNameFC = FC->getResult().generateNodeOutputName();
2590   auto weightsNameFC = FC->getWeights().generateNodeOutputName();
2591   auto biasNameFC = FC->getBias().generateNodeOutputName();
2592   auto inputNameFC = FC->getInput().generateNodeOutputName();
2593 
2594   // Lower everything and keep track of the lowered components source nodes via
2595   // the loweredMap.
2596   LoweredInfoMap loweredMapForProf;
2597   CompilationContext cctx(/* bindings */ nullptr, &loweredMapForProf);
2598   lower(profileF, cctx);
2599 
2600   // Check that the lowered graph only contains the lowered components of the
2601   // FC (MM and BA) and not the FC itself.
2602   auto *loweredFC = findNodeKindOrReturnNull<FullyConnectedNode>(profileF);
2603   auto *loweredMM = findNodeKindOrReturnNull<MatMulNode>(profileF);
2604   auto *loweredBA = findNodeKindOrReturnNull<BatchedAddNode>(profileF);
2605   ASSERT_FALSE(loweredFC);
2606   ASSERT_TRUE(loweredMM);
2607   ASSERT_TRUE(loweredBA);
2608   auto outputNameMM = loweredMM->getResult().generateNodeOutputName();
2609   auto outputNameBA = loweredBA->getResult().generateNodeOutputName();
2610 
2611   glow::profileQuantization(profilebindings, profileF,
2612                             cctx.precisionConfig.profConfig);
2613 
2614   // Compile/run to capture profile.
2615   profileEE.compile(CompilationMode::Infer);
2616   profileEE.run(profilebindings);
2617 
2618   // Get profiling infos and build new quantized graph, passing in the
2619   // loweredMapForProf to include the unlowered components in QI.
2620   profileF = profileEE.getModule().getFunctions().front();
2621   quantization::QuantizationConfiguration quantConfig{
2622       quantization::generateNodeProfilingInfos(profilebindings, profileF,
2623                                                loweredMapForProf)};
2624 
2625   // Verify that we have node profiling infos for the FC and the lowered
2626   // components of the FC (MM and BA).
2627   NodeProfilingInfo *FCPI = nullptr, *MMPI = nullptr, *BAPI = nullptr,
2628                     *FCWPI = nullptr, *FCBPI = nullptr, *FCIPI = nullptr;
2629   for (NodeProfilingInfo &NPI : quantConfig.infos) {
2630     if (NPI.nodeOutputName_ == outputNameFC) {
2631       FCPI = &NPI;
2632     } else if (NPI.nodeOutputName_ == outputNameMM) {
2633       MMPI = &NPI;
2634     } else if (NPI.nodeOutputName_ == outputNameBA) {
2635       BAPI = &NPI;
2636     } else if (NPI.nodeOutputName_ == weightsNameFC) {
2637       FCWPI = &NPI;
2638     } else if (NPI.nodeOutputName_ == biasNameFC) {
2639       FCBPI = &NPI;
2640     } else if (NPI.nodeOutputName_ == inputNameFC) {
2641       FCIPI = &NPI;
2642     }
2643   }
2644   ASSERT_TRUE(FCPI);
2645   ASSERT_TRUE(MMPI);
2646   ASSERT_TRUE(BAPI);
2647   ASSERT_TRUE(FCWPI);
2648   ASSERT_TRUE(FCBPI);
2649   ASSERT_TRUE(FCIPI);
2650 
2651   // Compute quantization parameters for verification.
2652   auto FCTQP = chooseQuantizationParams(
2653       FCPI->tensorProfilingParams_, quantConfig.schema, quantConfig.precision);
2654   auto MMTQP = chooseQuantizationParams(
2655       MMPI->tensorProfilingParams_, quantConfig.schema, quantConfig.precision);
2656   auto BATQP = chooseQuantizationParams(
2657       BAPI->tensorProfilingParams_, quantConfig.schema, quantConfig.precision);
2658   auto FCWTQP = chooseQuantizationParams(
2659       FCWPI->tensorProfilingParams_, quantConfig.schema, quantConfig.precision);
2660   auto FCBTQP =
2661       chooseQuantizationParams(FCBPI->tensorProfilingParams_,
2662                                quantConfig.schema, quantConfig.precisionBias);
2663   auto FCITQP = chooseQuantizationParams(
2664       FCIPI->tensorProfilingParams_, quantConfig.schema, quantConfig.precision);
2665 
2666   // Now create the same original function in the backend we're testing.
2667   ExecutionEngine backendEE;
2668   BackendClass backend;
2669   Backend *backendPtr = &backend;
2670   // backendEE.setBackend(&backend, /* ownsBackend */ false);
2671   Function *backendF = backendEE.getModule().createFunction("quantized");
2672   PlaceholderBindings backendbindings;
2673   createSimpleFCNet(backendbindings, backendEE, *backendF);
2674 
2675   // Lower the function given the backend's preferences for lowering.
2676   LoweredInfoMap loweredMapForQuant;
2677   CompilationContext cctx2(/* bindings */ nullptr, &loweredMapForQuant);
2678   lower(backendF, cctx2, backendPtr);
2679 
2680   // Check that the backend lowered the function as expected.
2681   auto *floatFC = findNodeKindOrReturnNull<FullyConnectedNode>(backendF);
2682   auto *floatMM = findNodeKindOrReturnNull<MatMulNode>(backendF);
2683   auto *floatBA = findNodeKindOrReturnNull<BatchedAddNode>(backendF);
2684   if (expectLoweredFC) {
2685     ASSERT_FALSE(floatFC);
2686     ASSERT_TRUE(floatMM);
2687     ASSERT_TRUE(floatBA);
2688   } else {
2689     ASSERT_TRUE(floatFC);
2690     ASSERT_FALSE(floatMM);
2691     ASSERT_FALSE(floatBA);
2692   }
2693 
2694   // Quantize the function given the current backend we're testing along with
2695   // the quantization infos gathered.
2696   quantConfig.enableRowwise = rowwiseQuantizeFC;
2697   quantConfig.assertAllNodesQuantized = true;
2698   quantization::quantizeFunction(backendF, quantConfig, *backendPtr,
2699                                  loweredMapForQuant);
2700 
2701   // Optimize the graph to remove dead code and optimize away unnecessary
2702   // quantize nodes. Note that we do not do a full compile call here, as we have
2703   // already lowered.
2704   ::glow::optimize(backendF, CompilationMode::Infer);
2705 
2706   // Check that the graph is still structured as expected, and that the
2707   // scales/offsets are set as found in TQP.
2708   auto *quantFC = findNodeKindOrReturnNull<FullyConnectedNode>(backendF);
2709   auto *quantMM = findNodeKindOrReturnNull<MatMulNode>(backendF);
2710   auto *quantBA = findNodeKindOrReturnNull<BatchedAddNode>(backendF);
2711   auto *quantRowwiseFC =
2712       findNodeKindOrReturnNull<RowwiseQuantizedFullyConnectedNode>(backendF);
2713 
2714   if (rowwiseQuantizeFC) {
2715     EXPECT_FALSE(quantMM);
2716     EXPECT_FALSE(quantBA);
2717     EXPECT_FALSE(quantFC);
2718 
2719     ASSERT_TRUE(quantRowwiseFC);
2720     EXPECT_EQ(quantRowwiseFC->getResult().getType()->getScale(), FCTQP.scale);
2721     EXPECT_EQ(quantRowwiseFC->getResult().getType()->getOffset(), FCTQP.offset);
2722 
2723     EXPECT_EQ(quantRowwiseFC->getBias().getElementType(), ElemKind::Int32QTy);
2724     // Bias scale was changed with the product inputScale * weightsScale only
2725     // if the product was larger.
2726     if (FCWTQP.scale * FCITQP.scale > FCBTQP.scale) {
2727       EXPECT_EQ(quantRowwiseFC->getBias().getType()->getScale(),
2728                 FCWTQP.scale * FCITQP.scale);
2729       EXPECT_EQ(quantRowwiseFC->getBias().getType()->getOffset(), 0);
2730     } else {
2731       EXPECT_EQ(quantRowwiseFC->getBias().getType()->getScale(), FCBTQP.scale);
2732       EXPECT_EQ(quantRowwiseFC->getBias().getType()->getOffset(), 0);
2733     }
2734   } else if (expectLoweredFC) {
2735     ASSERT_FALSE(quantFC);
2736     ASSERT_FALSE(quantRowwiseFC);
2737 
2738     ASSERT_TRUE(quantMM);
2739     EXPECT_EQ(quantMM->getResult().getType()->getScale(), MMTQP.scale);
2740     EXPECT_EQ(quantMM->getResult().getType()->getOffset(), MMTQP.offset);
2741 
2742     ASSERT_TRUE(quantBA);
2743     EXPECT_EQ(quantBA->getResult().getType()->getScale(), BATQP.scale);
2744     EXPECT_EQ(quantBA->getResult().getType()->getOffset(), BATQP.offset);
2745 
2746     EXPECT_EQ(quantBA->getSlice().getElementType(), ElemKind::Int32QTy);
2747     // Bias scale was changed with the product inputScale * weightsScale only
2748     // if the product was larger.
2749     if (FCWTQP.scale * FCITQP.scale > FCBTQP.scale) {
2750       EXPECT_EQ(quantBA->getSlice().getType()->getScale(),
2751                 FCWTQP.scale * FCITQP.scale);
2752       EXPECT_EQ(quantBA->getSlice().getType()->getOffset(), 0);
2753     } else {
2754       EXPECT_EQ(quantBA->getSlice().getType()->getScale(), FCBTQP.scale);
2755       EXPECT_EQ(quantBA->getSlice().getType()->getOffset(), 0);
2756     }
2757   } else {
2758     ASSERT_FALSE(quantRowwiseFC);
2759 
2760     ASSERT_TRUE(quantFC);
2761     EXPECT_EQ(quantFC->getResult().getType()->getScale(), FCTQP.scale);
2762     EXPECT_EQ(quantFC->getResult().getType()->getOffset(), FCTQP.offset);
2763 
2764     ASSERT_FALSE(quantMM);
2765     ASSERT_FALSE(quantBA);
2766 
2767     EXPECT_EQ(quantFC->getBias().getElementType(), ElemKind::Int32QTy);
2768     // Bias scale was changed with the product inputScale * weightsScale only
2769     // if the product was larger.
2770     if (FCWTQP.scale * FCITQP.scale > FCBTQP.scale) {
2771       EXPECT_EQ(quantFC->getBias().getType()->getScale(),
2772                 FCWTQP.scale * FCITQP.scale);
2773       EXPECT_EQ(quantFC->getBias().getType()->getOffset(), 0);
2774     } else {
2775       EXPECT_EQ(quantFC->getBias().getType()->getScale(), FCBTQP.scale);
2776       EXPECT_EQ(quantFC->getBias().getType()->getOffset(), 0);
2777     }
2778   }
2779 }
2780 
2781 /// Test that backends that do not lower FCs can find the quantization
2782 /// parameters of their nodes.
TEST(Quantization,TestProfileQuantizationOfUnloweredFC)2783 TEST(Quantization, TestProfileQuantizationOfUnloweredFC) {
2784   testProfileQuantizationOfFC<MockBackendUnloweredFC>(
2785       /* expectLoweredFC */ false, /* rowwiseQuantizeFC */ false);
2786 }
2787 
2788 /// Test that backends that do lower FCs can find the quantization parameters of
2789 /// their nodes.
TEST(Quantization,TestProfileQuantizationOfLoweredFC)2790 TEST(Quantization, TestProfileQuantizationOfLoweredFC) {
2791   testProfileQuantizationOfFC<MockBackendLoweredFC>(
2792       /* expectLoweredFC */ true, /* rowwiseQuantizeFC */ false);
2793 }
2794 
2795 /// Test that backends that do not lower FCs can find the quantization
2796 /// parameters of their nodes and correctly rowwise quantize.
TEST(Quantization,TestProfileQuantizationOfUnloweredFCRowwise)2797 TEST(Quantization, TestProfileQuantizationOfUnloweredFCRowwise) {
2798   testProfileQuantizationOfFC<MockBackendUnloweredFC>(
2799       /* expectLoweredFC */ false, /* rowwiseQuantizeFC */ true);
2800 }
2801 
2802 /// Test that backends that do lower FCs can find the quantization parameters of
2803 /// their nodes and correctly rowwise quantize even when lowering the FC.
TEST(Quantization,TestProfileQuantizationOfLoweredFCRowwise)2804 TEST(Quantization, TestProfileQuantizationOfLoweredFCRowwise) {
2805   testProfileQuantizationOfFC<MockBackendLoweredFC>(
2806       /* expectLoweredFC */ true, /* rowwiseQuantizeFC */ true);
2807 }
2808 
2809 /// Check that asserting quantization for the quantizer works as expected.
TEST(Quantization,CheckAssertQuantization)2810 TEST(Quantization, CheckAssertQuantization) {
2811   ExecutionEngine EE{};
2812   std::unique_ptr<Backend> backend(createBackend(EE.getBackendName()));
2813   auto &mod = EE.getModule();
2814   Function *F = mod.createFunction("main");
2815   auto *input = mod.createPlaceholder(ElemKind::FloatTy, {1, 3}, "input", true);
2816   auto *relu = F->createRELU("ReLU", input);
2817   PlaceholderBindings bindings;
2818   auto *save = F->createSave("ret", relu);
2819   bindings.allocate(save->getPlaceholder());
2820 
2821   quantization::QuantizationConfiguration quantConfig{
2822       {{input->getOutput().generateNodeOutputName(), {0.2f, 2.0f}},
2823        {relu->getResult().generateNodeOutputName(), {0.2f, 3.0f}}}};
2824   quantConfig.precision = ElemKind::Int16QTy;
2825   quantConfig.assertAllNodesQuantized = true;
2826 
2827   // Expect this to die because quantizeFunction() is passed with
2828   // assertAllNodesQuantized true, and the Interpreter backend does not support
2829   // Int16QTy ReLU.
2830   Function *QF = F->clone("quant_clone1");
2831   EXPECT_DEATH(quantization::quantizeFunction(QF, quantConfig, *backend), "");
2832 
2833   {
2834     Function *QF = F->clone("quant_clone2");
2835     quantConfig.assertAllNodesQuantized = false;
2836 
2837     // This works fine because quantizeFunction() is passed with
2838     // assertAllNodesQuantized false, and so the ReLU will not be quantized as
2839     // the Interpreter does not support Int16QTy ReLU.
2840     quantization::quantizeFunction(QF, quantConfig, *backend);
2841 
2842     auto *saveNode =
2843         llvm::dyn_cast<SaveNode>(QF->getNodeByName(save->getName()));
2844     ASSERT_TRUE(saveNode);
2845     auto *reluNode = llvm::dyn_cast<ReluNode>(saveNode->getInput().getNode());
2846     ASSERT_TRUE(reluNode);
2847     EXPECT_TRUE(!reluNode->getResult().getType()->isQuantizedType());
2848   }
2849 
2850   {
2851     Function *QF = F->clone("quant_clone3");
2852     quantConfig.assertAllNodesQuantized = true;
2853     KindSet doNotQuantizeKinds;
2854     doNotQuantizeKinds.insert(Kinded::Kind::ReluNodeKind);
2855 
2856     // This works fine because quantizeFunction() is passed with
2857     // assertAllNodesQuantized true, but we explicitly tell the quantizer to
2858     // keep ReLU in its original precision.
2859     quantization::quantizeFunction(QF, quantConfig, *backend,
2860                                    /* loweredMap */ {}, doNotQuantizeKinds);
2861 
2862     auto *saveNode =
2863         llvm::dyn_cast<SaveNode>(QF->getNodeByName(save->getName()));
2864     ASSERT_TRUE(saveNode);
2865     auto *reluNode = llvm::dyn_cast<ReluNode>(saveNode->getInput().getNode());
2866     ASSERT_TRUE(reluNode);
2867     EXPECT_TRUE(!reluNode->getResult().getType()->isQuantizedType());
2868   }
2869 }
2870 
2871 /// Check that we can quantize nodes that have some quantized outputs as unused,
2872 /// e.g. a TopK node where values is unused but indices is.
TEST(Quantization,QuantizationZeroUsersResult)2873 TEST(Quantization, QuantizationZeroUsersResult) {
2874   ExecutionEngine EE{};
2875   auto &mod = EE.getModule();
2876   PlaceholderBindings bindings;
2877   Function *F = mod.createFunction("main");
2878   auto *input =
2879       mod.createPlaceholder(ElemKind::FloatTy, {3, 1, 5}, "input", false);
2880 
2881   bindings.allocate(input)->getHandle() = {
2882       28, 4, 411, 19, 42, 0.4f, 0.4f, 0.4f, -0.4f, 0.45f, 7, 5, 9, 8, 100,
2883   };
2884 
2885   // Note we intentionally do not save the topk's values result.
2886   auto *TK = F->createTopK("TopK", input, 3);
2887   auto *SN = F->createSave("save_indices", TK->getIndices());
2888   bindings.allocate(SN->getPlaceholder());
2889 
2890   quantization::QuantizationConfiguration quantConfig{
2891       {{input->getOutput().generateNodeOutputName(), {0.2f, 2.0f}},
2892        {TK->getValues().generateNodeOutputName(), {0.2f, 3.0f}}}};
2893   quantConfig.assertAllNodesQuantized = true;
2894 
2895   std::unique_ptr<Backend> backend(createBackend(EE.getBackendName()));
2896   quantization::quantizeFunction(F, quantConfig, *backend);
2897 
2898   auto *qSN = llvm::dyn_cast<SaveNode>(F->getNodeByName(SN->getName()));
2899   ASSERT_TRUE(qSN);
2900   auto *qTK = llvm::dyn_cast<TopKNode>(qSN->getInput().getNode());
2901   ASSERT_TRUE(qTK);
2902   EXPECT_TRUE(qTK->getValues().getType()->isQuantizedType());
2903 }
2904 
2905 GLOW_INSTANTIATE_TEST_SUITE_P(Interpreter, Quantization,
2906                               ::testing::Values("Interpreter"));
2907 
2908 #ifdef GLOW_WITH_CPU
2909 GLOW_INSTANTIATE_TEST_SUITE_P(CPU, Quantization, ::testing::Values("CPU"));
2910 
2911 GLOW_INSTANTIATE_TEST_SUITE_P(
2912     InterpAndCPUProfAndQuant, Operator,
2913     ::testing::Combine(::testing::Values("Interpreter", "CPU"),
2914                        ::testing::Values("Interpreter", "CPU")));
2915 
2916 GLOW_INSTANTIATE_TEST_SUITE_P(
2917     InterpAndCPUProfAndQuant, InterpAndCPU,
2918     ::testing::Combine(::testing::Values("Interpreter", "CPU"),
2919                        ::testing::Values("Interpreter", "CPU")));
2920 
2921 #else
2922 GLOW_INSTANTIATE_TEST_SUITE_P(
2923     InterpreterProfAndQuant, Operator,
2924     ::testing::Combine(::testing::Values("Interpreter"),
2925                        ::testing::Values("Interpreter")));
2926 
2927 GLOW_INSTANTIATE_TEST_SUITE_P(
2928     Interpreter, InterpAndCPU,
2929     ::testing::Combine(::testing::Values("Interpreter"),
2930                        ::testing::Values("Interpreter")));
2931 #endif // GLOW_WITH_CPU
2932 
2933 #ifdef GLOW_WITH_OPENCL
2934 GLOW_INSTANTIATE_TEST_SUITE_P(
2935     InterpProfOpenCLQuant, Operator,
2936     ::testing::Combine(::testing::Values("Interpreter"),
2937                        ::testing::Values("OpenCL")));
2938 #endif // GLOW_WITH_OPENCL
2939 
2940 } // namespace glow
2941