// Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lite/operators/interpolate_op.h" #include #include #include "lite/core/device_info.h" #include "lite/core/op_lite.h" #include "lite/core/op_registry.h" #include "lite/kernels/mlu/bridges/test_helper.h" #include "lite/kernels/mlu/bridges/utility.h" #include "lite/kernels/npu/bridges/registry.h" namespace paddle { namespace lite { namespace subgraph { namespace mlu { template void ResizeNearestAlign(const lite::Tensor* x, lite::Tensor* out, bool with_align) { auto x_dims = x->dims(); int num = x_dims[0]; int channels = x_dims[1]; int hin = x_dims[2]; int win = x_dims[3]; int hout = out->dims()[2]; int wout = out->dims()[3]; dtype scale_w = (with_align) ? (static_cast(win - 1) / (wout - 1)) : (static_cast(win) / (wout)); dtype scale_h = (with_align) ? (static_cast(hin - 1) / (hout - 1)) : (static_cast(hin) / (hout)); const dtype* src = x->data(); dtype* dst = out->mutable_data(); int dst_stride_w = 1; int dst_stride_h = wout; int dst_stride_c = wout * hout; int dst_stride_batch = wout * hout * channels; int src_stride_w = 1; int src_stride_h = win; int src_stride_c = win * hin; int src_stride_batch = win * hin * channels; for (int n = 0; n < num; ++n) { for (int c = 0; c < channels; ++c) { int src_index = n * src_stride_batch + c * src_stride_c; for (int h = 0; h < hout; ++h) { for (int w = 0; w < wout; ++w) { int fw = (with_align) ? static_cast(scale_w * w + 0.5) : static_cast(scale_w * w); fw = (fw < 0) ? 0 : fw; int fh = (with_align) ? static_cast(scale_h * h + 0.5) : static_cast(scale_h * h); fh = (fh < 0) ? 0 : fh; int w_start = static_cast(fw); int h_start = static_cast(fh); int dst_index = n * dst_stride_batch + c * dst_stride_c + h * dst_stride_h + w * dst_stride_w; dst[dst_index] = src[src_index + w_start * src_stride_w + h_start * src_stride_h]; } } } } } template void BilinearInterpRef(const lite::Tensor* x, lite::Tensor* out, bool align_corners, int align_mode) { auto x_dims = x->dims(); int batch_size = x_dims[0]; int channel_size = x_dims[1]; auto x_h = x_dims[2]; auto x_w = x_dims[3]; CHECK_EQ(x_dims.size(), 4); auto out_dims = out->dims(); int out_h = out_dims[2]; int out_w = out_dims[3]; // copy from x if no change if (x_h == out_h && x_w == out_w) { out->CopyDataFrom(*x); return; } float ratio_h = 0.f; float ratio_w = 0.f; if (out_h > 1) { ratio_h = (align_corners) ? static_cast(x_h - 1) / (out_h - 1) : static_cast(x_h) / out_h; } if (out_w > 1) { ratio_w = (align_corners) ? static_cast(x_w - 1) / (out_w - 1) : static_cast(x_w) / out_w; } // naive bilinear interpolation auto x_data = x->data(); auto out_data = out->mutable_data(); bool align_flag = (align_mode == 0 && !align_corners); std::vector vy_n, vy_s; std::vector vd_n, vd_s; vy_n.reserve(out_h); vy_s.reserve(out_h); vd_n.reserve(out_h); vd_s.reserve(out_h); for (int k = 0; k < out_h; k++) { int yn = align_flag ? static_cast(ratio_h * (k + 0.5) - 0.5) : static_cast(ratio_h * k); yn = (yn > 0) ? yn : 0; int ys = (yn + 1) < (x_h - 1) ? (yn + 1) : (x_h - 1); float idx_src_y = ratio_h * (k + 0.5) - 0.5; idx_src_y = (idx_src_y > 0) ? idx_src_y : 0; float dn = align_flag ? idx_src_y - yn : ratio_h * k - yn; float ds = 1.f - dn; { vy_n[k] = yn; vy_s[k] = ys; vd_n[k] = dn; vd_s[k] = ds; } } std::vector vx_w, vx_e; std::vector vd_w, vd_e; vx_w.reserve(out_w); vx_e.reserve(out_w); vd_w.reserve(out_w); vd_e.reserve(out_w); for (int l = 0; l < out_w; l++) { int xw = align_flag ? static_cast(ratio_w * (l + 0.5) - 0.5) : static_cast(ratio_w * l); xw = (xw > 0) ? xw : 0; int xe = (xw + 1) < (x_w - 1) ? (xw + 1) : (x_w - 1); float idx_src_x = ratio_w * (l + 0.5) - 0.5; idx_src_x = (idx_src_x > 0) ? idx_src_x : 0; float dw = align_flag ? idx_src_x - xw : ratio_w * l - xw; float de = 1.f - dw; { vx_w[l] = xw; vx_e[l] = xe; vd_w[l] = dw; vd_e[l] = de; } } std::vector x_strides(x_dims.size(), 1); for (int idx = x_strides.size() - 2; idx >= 0; idx--) { x_strides[idx] = x_strides[idx + 1] * x_dims[idx + 1]; } for (int i = 0; i < batch_size; i++) { for (int j = 0; j < channel_size; j++) { for (int k = 0; k < out_h; k++) { for (int l = 0; l < out_w; l++) { DType x0 = x_data[i * x_strides[0] + j * x_strides[1] + vy_n[k] * x_strides[2] + vx_w[l] * x_strides[3]]; DType x1 = x_data[i * x_strides[0] + j * x_strides[1] + vy_s[k] * x_strides[2] + vx_w[l] * x_strides[3]]; DType x2 = x_data[i * x_strides[0] + j * x_strides[1] + vy_n[k] * x_strides[2] + vx_e[l] * x_strides[3]]; DType x3 = x_data[i * x_strides[0] + j * x_strides[1] + vy_s[k] * x_strides[2] + vx_e[l] * x_strides[3]]; *out_data = x0 * vd_s[k] * vd_e[l] + x1 * vd_n[k] * vd_e[l] + x2 * vd_s[k] * vd_w[l] + x3 * vd_n[k] * vd_w[l]; out_data++; } } } } } class InterpComputeTester { protected: // common attributes for this op. std::string x_var_name = "X"; std::string outsize_var_name = "OutSize"; std::string out_var_name = "Out"; std::string out_ref_var_name = "out_ref"; DDim dims_{{1, 2, 3, 4}}; Scope scope; std::string interp_method_ = "nearest"; float scale_ = -1.f; int out_h_ = -1; int out_w_ = -1; bool align_corners_ = true; int align_mode_ = 1; bool use_outsize_ = false; public: InterpComputeTester(const std::string& alias, DDim dims, std::string interp_method = "nearest", float scale = -1.f, int out_h = -1, int out_w = -1, bool align_corners = true, int align_mode = 1, bool use_outsize = false) : dims_(dims), interp_method_(interp_method), scale_(scale), out_h_(out_h), out_w_(out_w), align_corners_(align_corners), align_mode_(align_mode), use_outsize_(use_outsize) {} void Execute(float abs_error) { cpp::OpDesc op_desc; auto* x = scope.Var(x_var_name)->GetMutable(); auto* out = scope.Var(out_var_name)->GetMutable(); auto* outsize = scope.Var(outsize_var_name)->GetMutable(); auto* outref = scope.Var(out_ref_var_name)->GetMutable(); int out_h = out_h_; int out_w = out_w_; if (scale_ > 0) { out_h = static_cast(dims_[2] * scale_); out_w = static_cast(dims_[3] * scale_); } x->Resize(dims_); /* printf("----output tensor dims: %ld, %d, %d, %ld\n", dims_[0], out_h, * out_w, dims_[1]); */ std::vector out_shape_nchw = {dims_[0], dims_[1], out_h, out_w}; outref->Resize(out_shape_nchw); outsize->Resize({2}); FillTensor(x, -1.f, 1.f); if (use_outsize_) { outsize->mutable_data()[0] = out_h; outsize->mutable_data()[1] = out_w; outsize->set_persistable(true); } if (interp_method_ == "nearest") { op_desc.SetType("nearest_interp"); } else if (interp_method_ == "bilinear") { op_desc.SetType("bilinear_interp"); } else { LOG(FATAL) << "unsupport"; } op_desc.SetInput("X", {x_var_name}); if (use_outsize_) { op_desc.SetInput("OutSize", {outsize_var_name}); } op_desc.SetOutput("Out", {out_var_name}); op_desc.SetAttr("scale", scale_); op_desc.SetAttr("out_h", out_h_); op_desc.SetAttr("out_w", out_w_); op_desc.SetAttr("align_corners", align_corners_); op_desc.SetAttr("align_mode", align_mode_); op_desc.SetAttr("interp_method", interp_method_); auto op = CreateOp(op_desc, &scope); if (interp_method_ == "nearest") { ResizeNearestAlign(x, outref, align_corners_); } else if (interp_method_ == "bilinear") { BilinearInterpRef(x, outref, align_corners_, align_mode_); } int in = dims_[0], ic = dims_[1], ih = dims_[2], iw = dims_[3]; Tensor input_trans; input_trans.Resize(dims_); transpose(x->mutable_data(), input_trans.mutable_data(), {in, ic, ih, iw}, {0, 2, 3, 1}); x->CopyDataFrom(input_trans); if (use_outsize_) { LaunchOp(op, {x_var_name, outsize_var_name}, {out_var_name}); } else { LaunchOp(op, {x_var_name}, {out_var_name}); } auto* out_ref_data = outref->mutable_data(); Tensor output_trans; output_trans.Resize(out_shape_nchw); transpose( out->mutable_data(), output_trans.mutable_data(), {static_cast(dims_[0]), out_h, out_w, static_cast(dims_[1])}, {0, 3, 1, 2}); auto* out_data = output_trans.mutable_data(); for (int i = 0; i < out->dims().production(); ++i) { EXPECT_NEAR(out_data[i], out_ref_data[i], abs_error); } } }; void TestInterpOuthw(float abs_error = 2e-5) { for (auto x_dims : std::vector>{{3, 4, 8, 9}}) { /* for (auto interp_method : std::vector{"nearest", * "bilinear"}) { */ for (auto interp_method : std::vector{"nearest"}) { for (int out_h : {6, 8, 12}) { for (int out_w : {6, 9}) { printf("testcase %s: out_w %d, out_h %d\n", interp_method.c_str(), out_w, out_h); InterpComputeTester tester( "def", DDim(x_dims), interp_method, -1.f, out_h, out_w); tester.Execute(abs_error); } } } } } void TestInterpScale(float abs_error = 2e-5) { for (auto x_dims : std::vector>{{3, 4, 8, 9}}) { /* for (auto interp_method : std::vector{"nearest", * "bilinear"}) { */ for (auto interp_method : std::vector{"nearest"}) { for (float scale : {0.3f, 1.f, 1.7f}) { printf("testcase %s: scale: %f\n", interp_method.c_str(), scale); InterpComputeTester tester("def", DDim(x_dims), interp_method, scale); tester.Execute(abs_error); } } } } void TestInterpOutsize(float abs_error = 2e-5) { for (auto x_dims : std::vector>{{3, 4, 8, 9}}) { /* for (auto interp_method : std::vector{"nearest", * "bilinear"}) { */ for (auto interp_method : std::vector{"nearest"}) { printf("testcase %s: outsize: %d %d\n", interp_method.c_str(), 4, 4); InterpComputeTester tester( "def", DDim(x_dims), interp_method, -1, 4, 4, true, 1, true); tester.Execute(abs_error); } } } void TestInterpAlignCorners(float abs_error = 2e-5) { for (auto x_dims : std::vector>{{3, 4, 8, 9}}) { for (bool align_corners : {true, false}) { printf( "testcase nearest: scale: 0.4, out_w -1 out_h -1, align_corners %d\n", align_corners); InterpComputeTester tester( "def", DDim(x_dims), "nearest", 0.4, -1, -1, align_corners); tester.Execute(abs_error); } } } void TestInterpAlignMode(float abs_error = 2e-5) { for (auto x_dims : std::vector>{{3, 4, 8, 9}}) { for (bool align_corners : {true, false}) { for (int align_mode : {0, 1}) { printf( "testcase bilinear: scale: 0.7, out_w -1 out_h -1, align_corners " "%d, mode %d\n", align_corners, align_mode); InterpComputeTester tester("def", DDim(x_dims), "bilinear", 0.7, -1, -1, align_corners, align_mode); tester.Execute(abs_error); } } } } TEST(MLUBridges, interpolate) { float abs_error = 2e-5; TestInterpOuthw(abs_error); TestInterpScale(abs_error); // bug, not usable // TestInterpOutsize(abs_error); TestInterpAlignCorners(abs_error); // only for bilinear interp // TestInterpAlignMode(abs_error); } } // namespace mlu } // namespace subgraph } // namespace lite } // namespace paddle USE_SUBGRAPH_BRIDGE(nearest_interp, kMLU);