blob: 7b2bb4a7c50fc954237e09a32f71009f790b60d0 [file] [log] [blame]
/* Copyright 2017 The TensorFlow 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 "tensorflow/compiler/tf2xla/type_util.h"
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/array4d.h"
#include "tensorflow/compiler/xla/client/lib/constants.h"
#include "tensorflow/compiler/xla/client/lib/numeric.h"
#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
#include "tensorflow/core/framework/register_types.h"
#include "tensorflow/core/lib/math/math_util.h"
namespace tensorflow {
namespace {
// We implement bilinear interpolation by upsampling followed by convolution.
// The basic idea is as follows. To scale from NxN to RxR:
//
// 1. S := (N - 1) / gcd(N-1, R-1)
// 2. k := (R - 1) / gcd(N-1, R-1)
// 3. Convolution((2k-1)x(2k-1), stride=S, lhs_dilation=k, padding=k-1)
//
// For example, to Scale from 7x7 -> 15x15:
//
// 1. S := (7-1) / gcd(7-1, 15-1) = 6 / gcd(6, 14) = 6 / 2 = 3
// 2. k := (15 - 1) / gcd(7-1, 15-1) = 14 / gcd(6, 14) = 14 / 2 = 7
// 3. Convolution(15x15, stride=3, lhs_dilation=7, padding=2)
//
//
// The 7x7 -> 15x15 case is much too large to write out in full as an
// example. The smallest interesting example is 3x3 -> 4x4.
//
// S := 2
// k := 3
//
// 00 03 06 00 00 00 00 00 00 00 00 00 00 00 00 02 04 06
// 09 12 15 -> 00 00 00 00 00 00 00 00 00 00 00 -> 06 08 10 12
// 18 21 24 00 00 00 00 00 03 00 00 06 00 00 12 14 16 18
// 00 00 00 00 00 00 00 00 00 00 00 18 20 22 24
// 00 00 00 00 00 00 00 00 00 00 00
// 00 00 09 00 00 12 00 00 15 00 00
// 00 00 00 00 00 00 00 00 00 00 00
// 00 00 00 00 00 00 00 00 00 00 00
// 00 00 18 00 00 21 00 00 24 00 00
// 00 00 00 00 00 00 00 00 00 00 00
// 00 00 00 00 00 00 00 00 00 00 00
//
// with the following convolutional kernel, with stride [2, 2]:
// 1 2 3 2 1
// 2 4 6 4 2
// 1/9 * 3 6 9 6 3
// 2 4 6 4 2
// 1 2 3 2 1
// Note that the convolution kernel matrix is separable and thus we can instead
// use 2 consecutive 1D kernel of the dimension 2k-1, along each axis.
// Computes the size of the convolutional kernel and stride to use when resizing
// from in_size to out_size.
struct ResizeConvolutionDims {
// Size of the kernel to use.
std::vector<int64> kernel_size;
// Stride of the convolution to use.
std::vector<int64> stride;
};
ResizeConvolutionDims ComputeResizeConvolutionParameters(
absl::Span<const int64> in_size, absl::Span<const int64> out_size,
bool align_corners) {
CHECK_EQ(in_size.size(), out_size.size());
int num_spatial_dims = in_size.size();
ResizeConvolutionDims dims;
dims.kernel_size.resize(num_spatial_dims);
dims.stride.resize(num_spatial_dims);
for (int i = 0; i < num_spatial_dims; ++i) {
if (in_size[i] == 1) {
// We must handle input size 1 specially because XLA convolution does
// not allow stride 0.
dims.stride[i] = dims.kernel_size[i] = 1;
} else if (out_size[i] == 1) {
// If in_size[i] > 1 but out_size[i] == 1, then we slice out the first
// entry before resizing.
dims.stride[i] = dims.kernel_size[i] = 1;
} else {
// The scaling factor changes depending on the alignment of corners.
const int64 in_size_factor = align_corners ? in_size[i] - 1 : in_size[i];
const int64 out_size_factor =
align_corners ? out_size[i] - 1 : out_size[i];
int64 gcd = MathUtil::GCD(static_cast<uint64>(in_size_factor),
static_cast<uint64>(out_size_factor));
dims.stride[i] = in_size_factor / gcd;
dims.kernel_size[i] = out_size_factor / gcd;
}
}
return dims;
}
// The upper padding of the input needed by ConvGeneralDilated calls is
// determined by solving two related relationships (assuming rhs_dilation == 0):
// 1. dilated_input_dim = lower_padding + upper_padding
// + lhs_dilation * (in_size - 1) + 1
// 2. dilated_input_dim = (2 * dims.kernel-size - 1)
// + dims.stride * (out_size - 1)
int64 CalculateUpperPadding(int64 in_size, int64 out_size, int64 kernel_size,
int64 stride) {
return (2 * kernel_size - 1) + (out_size - 1) * stride - (kernel_size - 1) -
1 - (kernel_size * (in_size - 1));
}
// Form a 2D convolution kernel like:
// 1 2 3 2 1
// 2 4 6 4 2
// 1/9 * 3 6 9 6 3
// 2 4 6 4 2
// 1 2 3 2 1
// by multiplying two 1D kernels of the form:
// 1/3 * [1 2 3 2 1]
// If the 2D kernel would be very large, the 1D kernel can be applied once in
// each dimension due to the symmetry of the kernel along all axis to reduce the
// computational intensity.
xla::XlaOp Make1DKernel(xla::XlaBuilder* builder, int64 n) {
std::vector<float> kernel(n * 2 - 1);
for (int64 i = 0; i < n; ++i) {
float v = (i + 1.0f) / n;
kernel[i] = v;
kernel[n * 2 - 2 - i] = v;
}
return xla::ConstantR1<float>(builder, kernel);
}
// Kernels with more than 16 spatial elements are considered intense and the
// kernel should applied to each dimension independently.
const int64 kMax2DKernelSize = 16;
xla::XlaOp MakeBilinearResizeKernel(xla::XlaBuilder* builder,
absl::Span<const int64> kernel_size,
int64 channels) {
auto depthwise_kernel = xla::Broadcast(
xla::Zero(builder, xla::F32),
{(2 * kernel_size[0] - 1), (2 * kernel_size[1] - 1), channels, 1});
return xla::Mul(
xla::Add(depthwise_kernel, Make1DKernel(builder, kernel_size[1]),
/*broadcast_dimensions=*/{1}),
Make1DKernel(builder, kernel_size[0]),
/*broadcast_dimensions=*/{0});
}
xla::XlaOp MakeBilinearResizeKernelInDim(xla::XlaBuilder* builder,
absl::Span<const int64> kernel_size,
int64 channels, int64 dim) {
auto depthwise_kernel =
xla::Broadcast(xla::Zero(builder, xla::F32),
{dim == 0 ? (2 * kernel_size[0] - 1) : 1,
dim == 1 ? (2 * kernel_size[1] - 1) : 1, channels, 1});
return xla::Add(depthwise_kernel, Make1DKernel(builder, kernel_size[dim]),
/*broadcast_dimensions=*/{dim});
}
xla::XlaOp ResizeUsingDilationAndConvolution(xla::XlaBuilder* builder,
const xla::XlaOp& input,
const int num_spatial_dims,
std::vector<int64> in_size,
std::vector<int64> out_size,
const int64 channels,
const bool align_corners) {
// Picture for a 1x3 to 1x4 resize:
// stride = 2, kernel size = 3
// Input:
// 3 6 9
// Input with dilation and padding:
// 0 0 3 0 0 6 0 0 9 0 0
// Convolution kernel:
// 1/3 * [1 2 3 2 1]
// Output:
// 3 5 7 9
xla::ConvolutionDimensionNumbers dimension_numbers;
dimension_numbers.set_input_batch_dimension(0);
dimension_numbers.set_output_batch_dimension(0);
dimension_numbers.set_input_feature_dimension(num_spatial_dims + 1);
dimension_numbers.set_output_feature_dimension(num_spatial_dims + 1);
for (int i = 0; i < num_spatial_dims; ++i) {
dimension_numbers.add_input_spatial_dimensions(1 + i);
dimension_numbers.add_output_spatial_dimensions(1 + i);
dimension_numbers.add_kernel_spatial_dimensions(i);
}
dimension_numbers.set_kernel_input_feature_dimension(num_spatial_dims + 1);
dimension_numbers.set_kernel_output_feature_dimension(num_spatial_dims);
ResizeConvolutionDims dims =
ComputeResizeConvolutionParameters(in_size, out_size, align_corners);
xla::XlaOp output;
// Concatenation and padding below currently assumes num_spatial_dims is 2 to
// prevent needless code complexity.
CHECK_EQ(num_spatial_dims, 2)
<< "ResizeUsingDilationAndConvolution pads only 2 dimensions currently.";
std::vector<int64> upper_padding(num_spatial_dims);
for (int i = 0; i < num_spatial_dims; ++i) {
upper_padding[i] = dims.kernel_size[i] - 1;
}
xla::XlaOp input_data = input;
if (!align_corners) {
// When Tensorflow does not align_corners, the resize indexing can access
// beyond the upper bound and is instead clamped to prevent out of bounds
// reads. This is conceptually the same as extending the edges of the input.
// We emulate this by copying the last row/column of the input.
// Calculate what padding would be needed then determine how far to extend
// the border before lhs dilation.
std::vector<int64> num_extended(num_spatial_dims);
upper_padding[0] = CalculateUpperPadding(
in_size[0], out_size[0], dims.kernel_size[0], dims.stride[0]);
upper_padding[1] = CalculateUpperPadding(
in_size[1], out_size[1], dims.kernel_size[1], dims.stride[1]);
num_extended[0] = upper_padding[0] / (dims.kernel_size[0]);
num_extended[1] = upper_padding[1] / (dims.kernel_size[1]);
if (num_extended[0] > 0) {
auto slice =
xla::Slice(input_data, {0, in_size[0] - 1, 0, 0},
{1, in_size[0], in_size[1], channels}, {1, 1, 1, 1});
for (int i = 0; i < num_extended[0]; i++) {
input_data = xla::ConcatInDim(builder, {input_data, slice}, 1);
}
}
if (num_extended[1] > 0) {
auto slice =
xla::Slice(input_data, {0, 0, in_size[1] - 1, 0},
{1, in_size[0] + num_extended[0], in_size[1], channels},
{1, 1, 1, 1});
for (int i = 0; i < num_extended[1]; i++) {
input_data = xla::ConcatInDim(builder, {input_data, slice}, 2);
}
}
// Setting in_size to (in_size + num_extended) due to the above Slice and
// ConcatInDim. Recalculate needed padding after the above Slice/Concat.
upper_padding[0] =
CalculateUpperPadding(in_size[0] + num_extended[0], out_size[0],
dims.kernel_size[0], dims.stride[0]);
upper_padding[1] =
CalculateUpperPadding(in_size[1] + num_extended[1], out_size[1],
dims.kernel_size[1], dims.stride[1]);
}
// Split convolutions into independent dimensions if they would be a very
// large kernel.
if (dims.kernel_size[0] * dims.kernel_size[1] < kMax2DKernelSize) {
xla::XlaOp kernel =
MakeBilinearResizeKernel(builder, dims.kernel_size, channels);
output =
xla::ConvGeneralDilated(input_data, kernel, dims.stride,
/*padding=*/
{{dims.kernel_size[0] - 1, upper_padding[0]},
{dims.kernel_size[1] - 1, upper_padding[1]}},
/*lhs_dilation=*/dims.kernel_size,
/*rhs_dilation=*/{1, 1}, dimension_numbers,
/*feature_group_count=*/channels);
} else {
xla::XlaOp kernel0 =
MakeBilinearResizeKernelInDim(builder, dims.kernel_size, channels, 0);
output = xla::ConvGeneralDilated(
input_data, kernel0, {dims.stride[0], 1},
/*padding=*/
{{dims.kernel_size[0] - 1, upper_padding[0]}, {0, 0}},
/*lhs_dilation=*/{dims.kernel_size[0], 1},
/*rhs_dilation=*/{1, 1}, dimension_numbers,
/*feature_group_count=*/channels);
xla::XlaOp kernel1 =
MakeBilinearResizeKernelInDim(builder, dims.kernel_size, channels, 1);
output = xla::ConvGeneralDilated(
output, kernel1, {1, dims.stride[1]},
/*padding=*/
{{0, 0}, {dims.kernel_size[1] - 1, upper_padding[1]}},
/*lhs_dilation=*/{1, dims.kernel_size[1]},
/*rhs_dilation=*/{1, 1}, dimension_numbers,
/*feature_group_count=*/channels);
}
// Add broadcasts to handle expanding from a size == 1 dimension to a
// size > 1 dimension.
for (int i = 0; i < num_spatial_dims; ++i) {
if (in_size[i] == 1 && out_size[i] > 1) {
output = xla::Add(output, xla::ConstantR1<float>(builder, out_size[i], 0),
/*broadcast_dimensions=*/{1 + i});
}
}
return output;
}
xla::XlaOp ResizeUsingDilationAndConvolutionGradOp(xla::XlaBuilder* builder,
const xla::XlaOp& grad,
const int num_spatial_dims,
std::vector<int64> in_size,
std::vector<int64> grad_size,
const int64 channels,
const bool align_corners) {
ResizeConvolutionDims dims =
ComputeResizeConvolutionParameters(in_size, grad_size, align_corners);
// To form the backward convolution, we keep the kernel unchanged (it is
// already symmetric) and swap the roles of strides and LHS dilation.
xla::ConvolutionDimensionNumbers dimension_numbers;
dimension_numbers.set_input_batch_dimension(0);
dimension_numbers.set_output_batch_dimension(0);
dimension_numbers.set_input_feature_dimension(num_spatial_dims + 1);
dimension_numbers.set_output_feature_dimension(num_spatial_dims + 1);
for (int i = 0; i < num_spatial_dims; ++i) {
dimension_numbers.add_input_spatial_dimensions(i + 1);
dimension_numbers.add_output_spatial_dimensions(i + 1);
dimension_numbers.add_kernel_spatial_dimensions(i);
}
dimension_numbers.set_kernel_input_feature_dimension(num_spatial_dims + 1);
dimension_numbers.set_kernel_output_feature_dimension(num_spatial_dims);
xla::XlaOp output;
if (dims.kernel_size[0] * dims.kernel_size[1] < kMax2DKernelSize) {
xla::XlaOp kernel =
MakeBilinearResizeKernel(builder, dims.kernel_size, channels);
// Broadcast the input kernel where the forward op expanded from a size == 1
// dimension to a size > 1 dimension. This has the effect of summing the
// gradient contributions in that dimension.
for (int i = 0; i < num_spatial_dims; ++i) {
if (in_size[i] == 1 && grad_size[i] > 1) {
kernel =
xla::Add(kernel, xla::ConstantR1<float>(builder, grad_size[i], 0),
/*broadcast_dimensions=*/{i});
}
}
output = xla::ConvGeneralDilated(
grad, kernel, /*window_strides=*/dims.kernel_size,
/*padding=*/
{{dims.kernel_size[0] - 1, dims.kernel_size[0] - 1},
{dims.kernel_size[1] - 1, dims.kernel_size[1] - 1}},
/*lhs_dilation=*/dims.stride,
/*rhs_dilation=*/{1, 1}, dimension_numbers,
/*feature_group_count=*/channels);
} else {
xla::XlaOp kernel0 =
MakeBilinearResizeKernelInDim(builder, dims.kernel_size, channels, 0);
xla::XlaOp kernel1 =
MakeBilinearResizeKernelInDim(builder, dims.kernel_size, channels, 1);
// Broadcast the input kernel where the forward op expanded from a size == 1
// dimension to a size > 1 dimension. This has the effect of summing the
// gradient contributions in that dimension.
if (in_size[0] == 1 && grad_size[0] > 1) {
kernel0 =
xla::Add(kernel0, xla::ConstantR1<float>(builder, grad_size[0], 0),
/*broadcast_dimensions=*/{0});
}
if (in_size[1] == 1 && grad_size[1] > 1) {
kernel1 =
xla::Add(kernel0, xla::ConstantR1<float>(builder, grad_size[1], 0),
/*broadcast_dimensions=*/{1});
}
output = xla::ConvGeneralDilated(
grad, kernel0, /*window_strides=*/{dims.kernel_size[0], 1},
/*padding=*/
{{dims.kernel_size[0] - 1, dims.kernel_size[0] - 1}, {0, 0}},
/*lhs_dilation=*/{dims.stride[0], 1},
/*rhs_dilation=*/{1, 1}, dimension_numbers,
/*feature_group_count=*/channels);
output = xla::ConvGeneralDilated(
output, kernel1, /*window_strides=*/{1, dims.kernel_size[1]},
/*padding=*/
{{0, 0}, {dims.kernel_size[1] - 1, dims.kernel_size[1] - 1}},
/*lhs_dilation=*/{1, dims.stride[1]},
/*rhs_dilation=*/{1, 1}, dimension_numbers,
/*feature_group_count=*/channels);
}
// If in_size[i] > 1 and grad_size[i] == 1, pad the output in dimension i.
// Opposite of the slice performed by the forward op.
xla::PaddingConfig padding = xla::MakeNoPaddingConfig(4);
bool pad_output = false;
for (int i = 0; i < num_spatial_dims; ++i) {
if (in_size[i] > 1 && grad_size[i] == 1) {
pad_output = true;
padding.mutable_dimensions(1 + i)->set_edge_padding_high(in_size[i] - 1);
}
}
if (pad_output) {
output = xla::Pad(output, xla::ConstantR0<float>(builder, 0.0f), padding);
}
return output;
}
class ResizeBilinearOp : public XlaOpKernel {
public:
explicit ResizeBilinearOp(OpKernelConstruction* ctx) : XlaOpKernel(ctx) {
OP_REQUIRES_OK(ctx, ctx->GetAttr("align_corners", &align_corners_));
}
void Compile(XlaOpKernelContext* ctx) override {
xla::XlaBuilder* b = ctx->builder();
TensorShape input_shape = ctx->InputShape(0);
OP_REQUIRES(ctx, input_shape.dims() == 4,
errors::InvalidArgument("input must be 4-dimensional",
input_shape.DebugString()));
const int64 batch = input_shape.dim_size(0);
std::vector<int64> in_size = {input_shape.dim_size(1),
input_shape.dim_size(2)};
const int64 channels = input_shape.dim_size(3);
OP_REQUIRES(ctx, in_size[0] > 0 && in_size[1] > 0,
errors::InvalidArgument("input size must be positive, got [",
in_size[0], ",", in_size[1], "]"));
std::vector<int64> out_size;
OP_REQUIRES_OK(ctx, ctx->ConstantInputAsIntVector(1, &out_size));
OP_REQUIRES(ctx, out_size.size() == 2,
errors::InvalidArgument("output size must be length 2, got ",
out_size.size()));
OP_REQUIRES(ctx, out_size[0] > 0 && out_size[1] > 0,
errors::InvalidArgument("output size must be positive, got [",
out_size[0], ",", out_size[1], "]"));
const int num_spatial_dims = 2;
xla::XlaOp input = ctx->Input(0);
// If in_size[i] > 1 and out_size[i] == 1, slice out the first input in
// dimension i.
bool slice_input = false;
for (int i = 0; i < num_spatial_dims; ++i) {
if (in_size[i] > 1 && out_size[i] == 1) {
// If in_size[i] > 1 but out_size[i] == 1, then we slice out the first
// entry before resizing.
slice_input = true;
in_size[i] = 1;
}
}
if (slice_input) {
input =
xla::Slice(input, {0, 0, 0, 0},
{batch, in_size[0], in_size[1], channels}, {1, 1, 1, 1});
}
// Output is always type float.
input = xla::ConvertElementType(input, xla::F32);
// Special Case:
// Instead of doing a ResizeUsingDilationAndConvolution directly,
// while (out_size[0]-1) = c * 2^x * (in_size[0]-1) for x>1 c>1, resize the
// image to 2*(in_size[0]-1)+1 x-times and then resize by scale c(int here).
// Instead of resizing directly we resize it iteratively.
//
// Since bilinear resize can be broken down as 2 sequential linear
// operations along different dimensions.
// Given sufficient numerical stability and a<e<c and b<f<d, bilinear resize
// from image of size axb -> cxd is same as resizing axb -> exf -> cxd.
// This does not work in the case of align_corners_=false because of special
// padding requirements that cause multiple resizes to be very different
// from a single resize.
//
// This makes the convolutions kernels smaller and the operation faster.
xla::XlaOp output = input;
while (in_size != out_size) {
if (in_size[0] != 1 && in_size[1] != 1) {
std::vector<float> k = {
(static_cast<float>(out_size[0]) - 1) / ((in_size[0] - 1) * 2),
(static_cast<float>(out_size[1]) - 1) / ((in_size[1] - 1) * 2)};
if ((k[0] == std::floor(k[0])) && (k[1] == std::floor(k[1])) &&
k[0] > 1 && k[1] > 1 && align_corners_) {
std::vector<int64> next_out_size = {(in_size[0] - 1) * 2 + 1,
(in_size[1] - 1) * 2 + 1};
output = ResizeUsingDilationAndConvolution(b, input, num_spatial_dims,
in_size, next_out_size,
channels, align_corners_);
input = output;
in_size = next_out_size;
} else {
output = ResizeUsingDilationAndConvolution(b, input, num_spatial_dims,
in_size, out_size,
channels, align_corners_);
in_size = out_size;
}
} else {
output = ResizeUsingDilationAndConvolution(b, input, num_spatial_dims,
in_size, out_size, channels,
align_corners_);
in_size = out_size;
}
}
ctx->SetOutput(0, output);
}
private:
bool align_corners_;
};
REGISTER_XLA_OP(Name("ResizeBilinear").CompileTimeConstInput("size"),
ResizeBilinearOp);
class ResizeBilinearGradOp : public XlaOpKernel {
public:
explicit ResizeBilinearGradOp(OpKernelConstruction* ctx) : XlaOpKernel(ctx) {
OP_REQUIRES_OK(ctx, ctx->GetAttr("align_corners", &align_corners_));
OP_REQUIRES(
ctx, align_corners_ == true,
errors::Unimplemented("ResizeBilinearGrad with align_corners=False is "
"not yet implemented"));
DataType output_dtype;
OP_REQUIRES_OK(ctx, ctx->GetAttr("T", &output_dtype));
OP_REQUIRES_OK(ctx, DataTypeToPrimitiveType(output_dtype, &output_type_));
}
void Compile(XlaOpKernelContext* ctx) override {
xla::XlaBuilder* b = ctx->builder();
TensorShape input_shape = ctx->InputShape(1);
OP_REQUIRES(ctx, input_shape.dims() == 4,
errors::InvalidArgument("input must be 4-dimensional",
input_shape.DebugString()));
const int64 batch = input_shape.dim_size(0);
std::vector<int64> in_size = {input_shape.dim_size(1),
input_shape.dim_size(2)};
const int64 channels = input_shape.dim_size(3);
OP_REQUIRES(ctx, in_size[0] > 0 && in_size[1] > 0,
errors::InvalidArgument("input size must be positive, got [",
in_size[0], ",", in_size[1], "]"));
TensorShape grad_shape = ctx->InputShape(0);
OP_REQUIRES(ctx, grad_shape.dims() == 4,
errors::InvalidArgument("gradient must be 4-dimensional",
grad_shape.DebugString()));
const int64 grad_batch = grad_shape.dim_size(0);
const std::vector<int64> grad_size = {grad_shape.dim_size(1),
grad_shape.dim_size(2)};
const int64 grad_channels = grad_shape.dim_size(3);
OP_REQUIRES(ctx, batch == grad_batch,
errors::InvalidArgument(
"activations and gradients must have the same batch size (",
batch, " vs. ", grad_batch, ")"));
OP_REQUIRES(ctx, grad_size[0] > 0 && grad_size[1] > 0,
errors::InvalidArgument("gradient size must be positive, got [",
grad_size[0], ",", grad_size[1], "]"));
OP_REQUIRES(
ctx, channels == grad_channels,
errors::InvalidArgument(
"activations and gradients must have the same number of channels (",
channels, " vs. ", grad_channels, ")"));
const int num_spatial_dims = 2;
xla::XlaOp grad = ctx->Input(0);
xla::XlaOp output = grad;
while (in_size != grad_size) {
if (in_size[0] != 1 && in_size[1] != 1) {
std::vector<float> k = {
(static_cast<float>(grad_size[0]) - 1) / ((in_size[0] - 1) * 2),
(static_cast<float>(grad_size[1]) - 1) / ((in_size[1] - 1) * 2)};
if ((k[0] == std::floor(k[0])) && (k[1] == std::floor(k[1])) &&
k[0] > 1 && k[1] > 1) {
std::vector<int64> next_grad_size = {(in_size[0] - 1) * 2 + 1,
(in_size[1] - 1) * 2 + 1};
output = ResizeUsingDilationAndConvolutionGradOp(
b, grad, num_spatial_dims, in_size, next_grad_size, channels,
align_corners_);
grad = output;
in_size = next_grad_size;
} else {
output = ResizeUsingDilationAndConvolutionGradOp(
b, grad, num_spatial_dims, in_size, grad_size, channels,
align_corners_);
in_size = grad_size;
}
} else {
output = ResizeUsingDilationAndConvolutionGradOp(
b, grad, num_spatial_dims, in_size, grad_size, channels,
align_corners_);
in_size = grad_size;
}
}
output = xla::ConvertElementType(output, output_type_);
ctx->SetOutput(0, output);
}
private:
bool align_corners_;
xla::PrimitiveType output_type_;
};
REGISTER_XLA_OP(Name("ResizeBilinearGrad"), ResizeBilinearGradOp);
} // namespace
} // namespace tensorflow