| /** | |
| * Copyright (c) 2016-present, Facebook, Inc. | |
| * | |
| * 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 "caffe2/operators/upsample_op.h" | |
| #include "caffe2/utils/cpu_neon.h" | |
| #include "caffe2/utils/math.h" | |
| namespace caffe2 { | |
| template <> | |
| bool UpsampleBilinearOp<float, CPUContext>::RunOnDevice() { | |
| const auto& X = Input(0); | |
| if (InputSize() == 2) { | |
| const auto& scales = Input(1); | |
| CAFFE_ENFORCE_EQ(scales.dim(), 1); | |
| CAFFE_ENFORCE_EQ(scales.numel(), 2); | |
| const float* scales_data = scales.data<float>(); | |
| height_scale_ = scales_data[0]; | |
| width_scale_ = scales_data[1]; | |
| } | |
| const int batch_size = X.dim32(0); | |
| const int num_channels = X.dim32(1); | |
| const int input_height = X.dim32(2); | |
| const int input_width = X.dim32(3); | |
| // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) | |
| int output_width = input_width * width_scale_; | |
| // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) | |
| int output_height = input_height * height_scale_; | |
| auto* Y = Output( | |
| 0, | |
| {batch_size, num_channels, output_height, output_width}, | |
| at::dtype<float>()); | |
| const float* input = X.data<float>(); | |
| float* output = Y->mutable_data<float>(); | |
| int channels = num_channels * batch_size; | |
| const float rheight = (output_height > 1) | |
| // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) | |
| ? (float)(input_height - 1) / (output_height - 1) | |
| : 0.f; | |
| const float rwidth = | |
| // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) | |
| (output_width > 1) ? (float)(input_width - 1) / (output_width - 1) : 0.f; | |
| for (int h2 = 0; h2 < output_height; ++h2) { | |
| // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) | |
| const float h1r = rheight * h2; | |
| // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) | |
| const int h1 = h1r; | |
| const int h1p = (h1 < input_height - 1) ? 1 : 0; | |
| // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) | |
| const float h1lambda = h1r - h1; | |
| const float h0lambda = (float)1. - h1lambda; | |
| for (int w2 = 0; w2 < output_width; ++w2) { | |
| // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) | |
| const float w1r = rwidth * w2; | |
| // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) | |
| const int w1 = w1r; | |
| const int w1p = (w1 < input_width - 1) ? 1 : 0; | |
| // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) | |
| const float w1lambda = w1r - w1; | |
| const float w0lambda = (float)1. - w1lambda; | |
| const float* Xdata = &input[h1 * input_width + w1]; | |
| float* Ydata = &output[h2 * output_width + w2]; | |
| for (int c = 0; c < channels; ++c) { | |
| Ydata[0] = h0lambda * (w0lambda * Xdata[0] + w1lambda * Xdata[w1p]) + | |
| h1lambda * | |
| (w0lambda * Xdata[h1p * input_width] + | |
| w1lambda * Xdata[h1p * input_width + w1p]); | |
| Xdata += input_width * input_height; | |
| Ydata += output_width * output_height; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| template <> | |
| bool UpsampleBilinearGradientOp<float, CPUContext>::RunOnDevice() { | |
| const auto& dY = Input(0); | |
| const auto& X = Input(1); | |
| if (InputSize() == 3) { | |
| const auto& scales = Input(2); | |
| CAFFE_ENFORCE_EQ(scales.dim(), 1); | |
| CAFFE_ENFORCE_EQ(scales.numel(), 2); | |
| const float* scales_data = scales.data<float>(); | |
| height_scale_ = scales_data[0]; | |
| width_scale_ = scales_data[1]; | |
| } | |
| const auto inputDims = dY.sizes(); | |
| CAFFE_ENFORCE_EQ(4, inputDims.size()); | |
| const int batch_size = dY.dim32(0); | |
| const int num_channels = dY.dim32(1); | |
| const int input_height = dY.dim32(2); | |
| const int input_width = dY.dim32(3); | |
| const int output_height = X.dim32(2); | |
| const int output_width = X.dim32(3); | |
| auto* dX = Output( | |
| 0, | |
| {batch_size, num_channels, output_height, output_width}, | |
| at::dtype<float>()); | |
| math::Set<float, CPUContext>( | |
| dX->numel(), 0.0f, dX->mutable_data<float>(), &context_); | |
| const float* dYdata = dY.data<float>(); | |
| float* dXdata = dX->mutable_data<float>(); | |
| int channels = num_channels * batch_size; | |
| const float rheight = (input_height > 1) | |
| // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) | |
| ? (float)(output_height - 1) / (input_height - 1) | |
| : 0.f; | |
| const float rwidth = | |
| // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) | |
| (input_width > 1) ? (float)(output_width - 1) / (input_width - 1) : 0.f; | |
| for (int h2 = 0; h2 < input_height; ++h2) { | |
| // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) | |
| const float h1r = rheight * h2; | |
| // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) | |
| const int h1 = h1r; | |
| const int h1p = (h1 < output_height - 1) ? 1 : 0; | |
| // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) | |
| const float h1lambda = h1r - h1; | |
| const float h0lambda = (float)1. - h1lambda; | |
| for (int w2 = 0; w2 < input_width; ++w2) { | |
| // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) | |
| const float w1r = rwidth * w2; | |
| // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) | |
| const int w1 = w1r; | |
| const int w1p = (w1 < output_width - 1) ? 1 : 0; | |
| // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) | |
| const float w1lambda = w1r - w1; | |
| const float w0lambda = (float)1. - w1lambda; | |
| float* pos1 = &dXdata[h1 * output_width + w1]; | |
| const float* pos2 = &dYdata[h2 * input_width + w2]; | |
| for (int c = 0; c < channels; ++c) { | |
| pos1[0] += h0lambda * w0lambda * pos2[0]; | |
| pos1[w1p] += h0lambda * w1lambda * pos2[0]; | |
| pos1[h1p * output_width] += h1lambda * w0lambda * pos2[0]; | |
| pos1[h1p * output_width + w1p] += h1lambda * w1lambda * pos2[0]; | |
| pos1 += output_width * output_height; | |
| pos2 += input_width * input_height; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| REGISTER_CPU_OPERATOR(UpsampleBilinear, UpsampleBilinearOp<float, CPUContext>); | |
| REGISTER_CPU_OPERATOR( | |
| UpsampleBilinearGradient, | |
| UpsampleBilinearGradientOp<float, CPUContext>); | |
| // Input: X, output: Y | |
| OPERATOR_SCHEMA(UpsampleBilinear) | |
| .NumInputs(1, 2) | |
| .NumOutputs(1) | |
| .Arg("width_scale", "Scale along width dimension") | |
| .Arg("height_scale", "Scale along height dimension") | |
| .SetDoc(R"DOC( | |
| Resizes the spatial dimensions of the input using bilinear | |
| interpolation. The `width_scale` and `height_scale` arguments | |
| control the size of the output, which is given by: | |
| output_width = floor(input_width * width_scale) | |
| output_height = floor(output_height * height_scale) | |
| )DOC") | |
| .Input(0, "X", "Input tensor") | |
| .Input( | |
| 1, | |
| "scales", | |
| "1D, 2-element, Scales tensor, [height_scale, width_scale]") | |
| .Output(0, "Y", "Output tensor"); | |
| // Input: dY, output: dX | |
| OPERATOR_SCHEMA(UpsampleBilinearGradient) | |
| .NumInputs(2, 3) | |
| .NumOutputs(1) | |
| .Arg("width_scale", "Scale along width dimension") | |
| .Arg("height_scale", "Scale along height dimension"); | |
| class GetUpsampleBilinearGradient : public GradientMakerBase { | |
| using GradientMakerBase::GradientMakerBase; | |
| vector<OperatorDef> GetGradientDefs() override { | |
| if (def_.input().size() == 2) { | |
| // this is a hack to support the second input as dynamic | |
| // width_scale and height_scale to align with onnx change | |
| return SingleGradientDef( | |
| "UpsampleBilinearGradient", | |
| "", | |
| vector<string>{GO(0), I(0), I(1)}, | |
| vector<string>{GI(0)}); | |
| } | |
| return SingleGradientDef( | |
| "UpsampleBilinearGradient", | |
| "", | |
| vector<string>{GO(0), I(0)}, | |
| vector<string>{GI(0)}); | |
| } | |
| }; | |
| REGISTER_GRADIENT(UpsampleBilinear, GetUpsampleBilinearGradient); | |
| } // namespace caffe2 |