Normalizer performance improvements (#3201)

* Simplify Normalizer as the spec only requires support for 2D input.

Tried using eigen (LpNorm<1>(), and norm()) on each row but that was much slower.

* Remove unused variable
This commit is contained in:
Scott McKay 2020-03-13 22:15:44 +10:00 committed by GitHub
parent 890cb78b20
commit e9d5ed270f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 126 additions and 132 deletions

View file

@ -43,146 +43,137 @@ ONNX_CPU_OPERATOR_ML_KERNEL(
Normalizer);
template <typename T>
void NormalizeMax(const gsl::span<const T>& in, gsl::span<float>& out,
int64_t offset, int64_t stride, int64_t increment_by) {
float max = std::numeric_limits<float>::lowest();
void NormalizeMax(const T* in, float* out, int64_t num_batches, int64_t batch_size) {
for (int b = 0; b < num_batches; ++b) {
float max = std::numeric_limits<float>::lowest();
for (int64_t i = offset, s = 0; s < stride; ++s, i += increment_by) {
max = std::max(max, static_cast<float>(in[i]));
}
if (max != 0.f) {
for (int64_t i = offset, s = 0; s < stride; ++s, i += increment_by) {
out[i] = static_cast<float>(in[i]) / max;
for (int i = 0; i < batch_size; ++i) {
max = std::max(max, static_cast<float>(*in++));
}
} else {
for (int64_t i = offset, s = 0; s < stride; ++s, i += increment_by) {
out[i] = static_cast<float>(in[i]);
in -= batch_size;
if (max != 0.f) {
for (int i = 0; i < batch_size; ++i) {
*out++ = static_cast<float>(*in++) / max;
}
} else {
for (int i = 0; i < batch_size; ++i) {
*out++ = static_cast<float>(*in++);
}
}
}
}
template <typename T>
void NormalizeL1(const gsl::span<const T>& in, gsl::span<float>& out,
int64_t offset, int64_t stride, int64_t increment_by) {
float sum = 0.f;
static void NormalizeL1(const T* in, float* out, int64_t num_batches, int64_t batch_size) {
for (int b = 0; b < num_batches; ++b) {
float sum = 0.f;
for (int64_t i = offset, s = 0; s < stride; ++s, i += increment_by) {
sum += static_cast<float>(std::abs(in[i]));
}
if (sum != 0.f) {
for (int64_t i = offset, s = 0; s < stride; ++s, i += increment_by) {
out[i] = static_cast<float>(in[i]) / sum;
for (int i = 0; i < batch_size; ++i) {
sum += static_cast<float>(std::abs(*in++));
}
} else {
for (int64_t i = offset, s = 0; s < stride; ++s, i += increment_by) {
out[i] = static_cast<float>(in[i]);
in -= batch_size;
if (sum != 0.f) {
for (int i = 0; i < batch_size; ++i) {
*out++ = static_cast<float>(*in++) / sum;
}
} else {
for (int i = 0; i < batch_size; ++i) {
*out++ = static_cast<float>(*in++);
}
}
}
}
template <typename T>
void NormalizeL2(const gsl::span<const T>& in, gsl::span<float>& out,
int64_t offset, int64_t stride, int64_t increment_by) {
float sum = 0.f;
for (int64_t i = offset, s = 0; s < stride; ++s, i += increment_by) {
auto x = in[i];
auto x_sq = static_cast<float>(x * x);
out[i] = x_sq;
sum += x_sq;
}
void NormalizeL2(const T* in, float* out, int64_t num_batches, int64_t batch_size) {
for (int b = 0; b < num_batches; ++b) {
float sum = 0.f;
if (sum != 0.f) {
for (int64_t i = offset, s = 0; s < stride; ++s, i += increment_by) {
auto x = in[i];
auto x_sq = out[i];
if (x < 0)
out[i] = std::sqrt(x_sq / sum) * -1;
else
out[i] = std::sqrt(x_sq / sum);
for (int i = 0; i < batch_size; ++i) {
auto x = *in++;
auto x_sq = static_cast<float>(x * x);
*out++ = x_sq;
sum += x_sq;
}
} else {
for (int64_t i = offset, s = 0; s < stride; ++s, i += increment_by) {
out[i] = static_cast<float>(in[i]);
in -= batch_size;
out -= batch_size;
if (sum != 0.f) {
for (int i = 0; i < batch_size; ++i) {
auto x = *in++;
auto x_sq = *out;
*out++ = (x < 0) ? std::sqrt(x_sq / sum) * -1 : std::sqrt(x_sq / sum);
}
} else {
for (int i = 0; i < batch_size; ++i) {
*out++ = static_cast<float>(*in++);
}
}
}
}
template <typename T>
void Normalizer::Normalize(OpKernelContext* context) const {
Status Normalizer::Normalize(OpKernelContext* context) const {
const Tensor& X = *context->Input<Tensor>(0);
const TensorShape& x_shape = X.Shape();
const auto data_size = x_shape.Size();
if (x_shape.NumDimensions() > 2) {
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Rank of input to Normalized must be less than 2. Got ",
x_shape.NumDimensions());
}
const auto& x_dims = x_shape.GetDims();
int64_t num_batches = x_dims.size() == 1 ? 1 : x_dims[0];
int64_t batch_size = x_dims.size() == 1 ? x_dims[0] : x_dims[1];
Tensor* Y = context->Output(0, x_shape);
auto input = gsl::make_span(X.template Data<T>(), data_size);
auto output = gsl::make_span(Y->template MutableData<float>(), data_size);
const T* input = X.template Data<T>();
float* output = Y->MutableData<float>();
int64_t stride = x_dims.size() == 1 ? x_dims[0] : x_dims[1];
int64_t loops = data_size / stride;
// we normalize on axis 1 so if there are more than 2 dimensions we need to increment the index
// by more than 1 as we process the stride
// for 1 and 2 dimension tensors we're normalizing across the row/s, so increment_by is 1
//
// e.g. if you have a tensor of shape {2, 2, 3}
// [[[ 1, 2, 3],
// [ 4, 5, 6]],
// [[ 7, 8, 9],
// [10, 11, 12]]]
//
// we want to normalize (1, 4), (2, 5), (3, 6),
// (7, 10), (8, 11), (9, 12)
// so the stride would be 2, and the increment_by would be 3.
//
// we process a block of stride * increment_by entries before we need to skip to the next row of the 2nd dimension.
// the offset starts at 0 and increases by 1 each loop, for increment_by loops.
// the offset then jumps by stride * increment
int64_t increment_by = x_dims.size() > 1 ? x_shape.SizeFromDimension(2) : 1;
for (int64_t n = 0; n < loops; ++n) {
int64_t offset = (n % increment_by) + ((n / increment_by) * (stride * increment_by));
switch (normalization_) {
case NORMALIZE::NMAX: {
NormalizeMax(input, output, offset, stride, increment_by);
break;
}
case NORMALIZE::L1: {
NormalizeL1(input, output, offset, stride, increment_by);
break;
}
case NORMALIZE::L2: {
NormalizeL2(input, output, offset, stride, increment_by);
break;
}
default: {
ORT_THROW("Unexpected NORMALIZE value of ", normalization_);
}
switch (normalization_) {
case NORMALIZE::NMAX: {
NormalizeMax(input, output, num_batches, batch_size);
break;
}
case NORMALIZE::L1: {
NormalizeL1(input, output, num_batches, batch_size);
break;
}
case NORMALIZE::L2: {
NormalizeL2(input, output, num_batches, batch_size);
break;
}
default: {
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Unexpected NORMALIZE value of ", normalization_);
}
}
return Status::OK();
}
// MLTypeCallDispather implementation wrapper
template <class T>
struct Normalizer::CallNormalizerImpl {
void operator()(const Normalizer* norm, OpKernelContext* ctx) const {
norm->Normalize<T>(ctx);
Status operator()(const Normalizer* norm, OpKernelContext* ctx) const {
return norm->Normalize<T>(ctx);
}
};
Status Normalizer::Compute(OpKernelContext* context) const {
const auto* input_tensor_ptr = context->Input<Tensor>(0);
ORT_ENFORCE(input_tensor_ptr != nullptr);
const auto& input_tensor_ptr = *context->Input<Tensor>(0);
utils::MLTypeCallDispatcher<CallNormalizerImpl, float, double, int64_t, int32_t> t_disp(input_tensor_ptr->GetElementType());
t_disp.Invoke(this, context);
return Status::OK();
utils::MLTypeCallDispatcherRet<Status, CallNormalizerImpl, float, double, int64_t, int32_t>
t_disp(input_tensor_ptr.GetElementType());
auto status = t_disp.Invoke(this, context);
return status;
}
} // namespace ml

View file

@ -25,9 +25,9 @@ class Normalizer final : public OpKernel {
private:
template <typename T>
void Normalize(OpKernelContext* context) const;
Status Normalize(OpKernelContext* context) const;
template<class>
template <class>
struct CallNormalizerImpl;
NORMALIZE normalization_;

View file

@ -21,6 +21,8 @@ static void RunTest(const vector<T>& input,
test.AddInput("X", dims, input);
test.AddOutput("Y", dims, output);
// output 'norm' so if a test fails we know which one
std::cout << "norm=" << norm << "\n";
test.Run(expect_result, expect_error_message);
}
@ -120,6 +122,23 @@ TEST(Normalizer, SingleDimensionFloat) {
RunTests(input, dims, max_output, l1_output, l2_output);
}
TEST(Normalizer, TwoDimensionFloat) {
std::vector<int64_t> dims = {2, 3};
std::vector<float> input = {-1.0856306f, 0.99734545f, 0.2829785f,
-1.50629471f, -0.57860025f, 1.65143654f};
std::vector<float> max_output{-1.0885202f, 1.f, 0.2837317f,
-0.91211176f, -0.35036176f, 1.f};
std::vector<float> l1_output{-0.45885524f, 0.42154038f, 0.11960436f,
-0.40314806f, -0.15485784f, 0.44199413f};
std::vector<float> l2_output{-0.7232126f, 0.6643998f, 0.18851127f,
-0.65239084f, -0.25059736f, 0.7152532f};
RunTests(input, dims, max_output, l1_output, l2_output);
}
TEST(Normalizer, TwoDimensionDouble) {
std::vector<int64_t> dims = {2, 3};
std::vector<double> input = {-1.0856306, 0.99734545, 0.2829785,
@ -137,39 +156,23 @@ TEST(Normalizer, TwoDimensionDouble) {
RunTests(input, dims, max_output, l1_output, l2_output);
}
TEST(Normalizer, ThreeDimensionInt32) {
std::vector<int64_t> dims = {2, 3, 4};
std::vector<int32_t> input = {-242, -42, 126, -86,
-67, -9, 149, -63,
-44, -43, 220, 218,
TEST(Normalizer, TwoDimensionInt) {
std::vector<int64_t> dims = {3, 2};
std::vector<int32_t> input = {-242, -42,
126, -86,
-67, -9};
100, 38, 73, 149,
-93, 117, -125, -63,
90, -142, -14, -86};
std::vector<float> max_output{5.7619047f, 1.f,
1.f, -0.6825397f,
7.4444447f, 1.f};
std::vector<float> max_output{5.5f, 4.6666665f, 0.57272726f, -0.39449543f,
1.5227273f, 1.f, 0.67727274f, -0.28899083f,
1.f, 4.7777777f, 1.f, 1.f,
std::vector<float> l1_output{-0.85211265f, -0.14788732f,
0.5943396f, -0.4056604f,
-0.8815789f, -0.11842106f};
1.f, 0.32478634f, 1.f, 1.f,
-0.93f, 1.f, -1.7123288f, -0.42281878f,
0.9f, -1.2136753f, -0.19178082f, -0.5771812f};
std::vector<float> l1_output{-0.6855524f, -0.44680852f, 0.25454545f, -0.23433243f,
-0.1898017f, -0.09574468f, 0.3010101f, -0.17166212f,
-0.12464589f, -0.4574468f, 0.44444445f, 0.59400547f,
0.3533569f, 0.12794612f, 0.3443396f, 0.5f,
-0.3286219f, 0.3939394f, -0.5896226f, -0.21140939f,
0.3180212f, -0.4781145f, -0.06603774f, -0.2885906f};
std::vector<float> l2_output{-0.94928247f, -0.6910363f, 0.4284698f, -0.3543899f,
-0.26281786f, -0.1480792f, 0.5066825f, -0.25961122f,
-0.17259681f, -0.7074895f, 0.74812186f, 0.89833724f,
0.6114293f, 0.2022622f, 0.5019584f, 0.8132732f,
-0.56862926f, 0.62275463f, -0.85951775f, -0.34386718f,
0.55028635f, -0.7558219f, -0.09626599f, -0.469406f};
std::vector<float> l2_output{-0.98527145f, -0.17099753f,
0.82594985f, -0.56374353f,
-0.9910982f, -0.13313259f};
RunTests(input, dims, max_output, l1_output, l2_output);
}