text: #ifndef CAFFE2_INT8_TEST_UTILS_H_
  #define CAFFE2_INT8_TEST_UTILS_H_
  
  #include "caffe2/core/common.h"
  #include "caffe2/core/context.h"
  #include "caffe2/core/operator.h"
  #include "caffe2/core/tensor.h"
  #include "caffe2/core/tensor_int8.h"
  
  #include <array>
  #include <cmath>
  #include <random>
  
  #include "gtest/gtest.h"
  
  namespace caffe2 {
  
  // for quantized Add, the error shouldn't exceed 2 * scale
  inline float addErrorTolerance(float scale) {
    return 2 * scale;
  }
  
  inline std::unique_ptr<int8::Int8TensorCPU> q(
      const std::vector<int64_t>& dims) {
    auto r = caffe2::make_unique<int8::Int8TensorCPU>();
    r->scale = 0.01;
    r->zero_point = static_cast<int32_t>(std::numeric_limits<uint8_t>::max()) / 2;
    r->t.Resize(dims);
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<uint8_t> dis;
    for (auto i = 0; i < r->t.numel(); ++i) {
      r->t.mutable_data<uint8_t>()[i] = dis(gen);
    }
    return r;
  }
  
  inline std::unique_ptr<int8::Int8TensorCPU> biasq(
      const std::vector<int64_t>& dims,
      double scale) {
    auto r = caffe2::make_unique<int8::Int8TensorCPU>();
    r->scale = scale;
    r->zero_point = 0;
    r->t.Resize(dims);
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_real_distribution<float> dis(-1, 1);
    for (auto i = 0; i < r->t.numel(); ++i) {
      r->t.mutable_data<int32_t>()[i] =
          static_cast<int32_t>(dis(gen) / scale + r->zero_point);
    }
    return r;
  }
  
  inline std::unique_ptr<TensorCPU> dq(const int8::Int8TensorCPU& XQ) {
    auto r = caffe2::make_unique<Tensor>(CPU);
    r->Resize(XQ.t.sizes());
    for (auto i = 0; i < r->numel(); ++i) {
      r->mutable_data<float>()[i] =
          (static_cast<int32_t>(XQ.t.data<uint8_t>()[i]) - XQ.zero_point) *
          XQ.scale;
    }
    return r;
  }
  
  inline std::unique_ptr<TensorCPU> biasdq(const int8::Int8TensorCPU& XQ) {
    auto r = caffe2::make_unique<Tensor>(CPU);
    r->Resize(XQ.t.sizes());
    for (auto i = 0; i < r->numel(); ++i) {
      r->mutable_data<float>()[i] =
          (XQ.t.data<int32_t>()[i] - XQ.zero_point) * XQ.scale;
    }
    return r;
  }
  
  #define EXPECT_TENSOR_EQ(_YA, _YE)                                     \
    do {                                                                 \
      EXPECT_TRUE((_YA).sizes() == (_YE).sizes());                       \
      for (auto i = 0; i < (_YA).numel(); ++i) {                         \
        EXPECT_FLOAT_EQ((_YA).data<float>()[i], (_YE).data<float>()[i]); \
      }                                                                  \
    } while (0);
  
  #define EXPECT_TENSOR_APPROX_EQ(_YA, _YE, _tol)                            \
    do {                                                                     \
      EXPECT_TRUE((_YA).sizes() == (_YE).sizes());                           \
      for (auto i = 0; i < (_YA).numel(); ++i) {                             \
        EXPECT_NEAR((_YA).data<float>()[i], (_YE).data<float>()[i], (_tol)); \
      }                                                                      \
    } while (0);
  
  inline void int8Copy(int8::Int8TensorCPU* dst, const int8::Int8TensorCPU& src) {
    dst->zero_point = src.zero_point;
    dst->scale = src.scale;
    dst->t.CopyFrom(src.t);
  }
  
  inline void add_input(
      const vector<int64_t>& shape,
      const vector<float>& values,
      const string& name,
      Workspace* ws) {
    // auto* t = ws->CreateBlob(name)->GetMutable<TensorCPU>();
    auto t = caffe2::make_unique<Tensor>(CPU);
    t->Resize(shape);
    std::copy(values.begin(), values.end(), t->mutable_data<float>());
    BlobGetMutableTensor(ws->CreateBlob(name), CPU)->CopyFrom(*t);
  }
  
  inline int randomInt(int a, int b) {
    static std::random_device rd;
    static std::mt19937 gen(rd());
    return std::uniform_int_distribution<int>(a, b)(gen);
  }
  
  } // namespace caffe2
  
  #endif // CAFFE2_INT8_TEST_UTILS_H_
  