blob: 375c420a0440e6957fac5e860df01a485ea1f0ba [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/*!
* Copyright (c) 2018 by Contributors
* \file adaptive_avg_pooling.cu
* \brief adaptive average pooling operator
* \author Hang Zhang
*/
#include <cuda_runtime_api.h>
#include <algorithm>
#include "adaptive_avg_pooling-inl.h"
#define START_IND(a, b, c) static_cast<int>(floor(static_cast<float>(a * c) / b))
#define END_IND(a, b, c) static_cast<int>(ceil(static_cast<float>((a + 1) * c) / b))
#define CUDA_MAX_THREADS 1024 // this is safe, in reality 256 is our limit
namespace mxnet {
namespace op {
using namespace mshadow;
template<typename In, typename Out>
struct ScalarConvert {
static __host__ __device__ __forceinline__ Out to(const In v) { return (Out) v; }
};
/*
* Description:
* this function adaptively average pools an input 4D tensor along dimensions 2 and 3
* 4D input, 4D output
*/
template <typename T>
__global__ void adaptiveaveragepool(T *input, T *output,
int isizeH, int isizeW,
int osizeH, int osizeW,
int64_t istrideD, int64_t istrideH, int64_t istrideW) {
// iterators on output pixels
int oh, ow;
// select input/output plane based on thread/block ID
int o_plane = blockIdx.x;
int i_plane = o_plane;
output = output + o_plane*osizeH*osizeW;
input = input + i_plane*istrideD;
int ostartH = blockDim.y*blockIdx.y + threadIdx.y;
int oendH = osizeH;
const int ostepH = blockDim.y*gridDim.y;
int ostartW = threadIdx.x;
int oendW = osizeW;
const int ostepW = blockDim.x;
// For all output pixels...
for (oh = ostartH; oh < oendH; oh += ostepH) {
int istartH = START_IND(oh, osizeH, isizeH);
int iendH = END_IND(oh, osizeH, isizeH);
int kH = iendH - istartH;
for (ow = ostartW; ow < oendW; ow += ostepW) {
int istartW = START_IND(ow, osizeW, isizeW);
int iendW = END_IND(ow, osizeW, isizeW);
int kW = iendW - istartW;
// Compute the average pooling over corresponding input pixels
T *ptr_input = input + istartH*istrideH + istartW*istrideW;
T *ptr_output = output + oh*osizeW + ow;
T sum = ScalarConvert<int, T>::to(0);
int ih, iw;
for (ih = 0; ih < kH; ++ih) {
for (iw = 0; iw < kW; ++iw) {
T val = ptr_input[iw*istrideW];
sum += val;
}
ptr_input += istrideH; // next input line
}
// Update output
*ptr_output = sum / kH / kW;
}
}
}
/*
* Description:
* this function computes the gradInput from gradOutput
* (uses atomic add)
*/
template <typename T>
__global__ void atomicadaptiveaveragegradinput(
T *gradInput, T *gradOutput,
int isizeH, int isizeW, int osizeH, int osizeW
) {
// iterators on output indices
int oh, ow;
// select input/output plane based on thread/block ID
int o_plane = blockIdx.x;
int i_plane = o_plane;
gradOutput = gradOutput + o_plane*osizeW*osizeH;
gradInput = gradInput + i_plane*isizeW*isizeH;
int ostartH = blockDim.y*blockIdx.y + threadIdx.y;
int oendH = osizeH;
int ostepH = blockDim.y*gridDim.y;
int ostartW = threadIdx.x;
int oendW = osizeW;
int ostepW = blockDim.x;
// For all output pixels...
for (oh = ostartH; oh < oendH; oh += ostepH) {
int istartH = START_IND(oh, osizeH, isizeH);
int iendH = END_IND(oh, osizeH, isizeH);
int kH = iendH - istartH;
for (ow = ostartW; ow < oendW; ow += ostepW) {
int istartW = START_IND(ow, osizeW, isizeW);
int iendW = END_IND(ow, osizeW, isizeW);
int kW = iendW - istartW;
// Compute the gradients for over corresponding input pixels
T *ptr_gradInput = gradInput + istartH*isizeW + istartW;
T *ptr_gradOutput = gradOutput + oh*osizeW + ow;
T grad_delta = *ptr_gradOutput / kW / kH;
int ih, iw;
for (ih = 0; ih < kH; ++ih) {
for (iw = 0; iw < kW; ++iw) {
// atomic add since different threads could update same variable
atomicAdd(&(ptr_gradInput[iw]), grad_delta);
}
ptr_gradInput += isizeW; // next input line
}
}
}
}
template<typename xpu, typename DType, typename AccReal>
void AdaptiveAvgPoolUpdateOutput(mshadow::Stream<gpu> *s,
const std::vector<TBlob> &input,
const std::vector<TBlob> &output) {
Tensor<xpu, 4, DType> itensor = input[0].get<xpu, 4, DType>(s);
Tensor<xpu, 4, DType> otensor = output[0].get<xpu, 4, DType>(s);
DType *input_data = itensor.dptr_;
DType *output_data = otensor.dptr_;
int64_t sizeB = itensor.size(0);
int64_t sizeD = itensor.size(1);
int64_t isizeH = itensor.size(2);
int64_t isizeW = itensor.size(3);
int64_t istrideD = get_stride<xpu, 4, DType>(itensor, 1);
int64_t istrideH = get_stride<xpu, 4, DType>(itensor, 2);
int64_t istrideW = get_stride<xpu, 4, DType>(itensor, 3);
int64_t osizeH = otensor.size(2);
int64_t osizeW = otensor.size(3);
// cuda blocks & threads:
int blocksH = max(static_cast<int>(16L / sizeD), 1);
dim3 blocks(sizeB * sizeD, blocksH);
dim3 threads(32, 8);
cudaStream_t stream = mshadow::Stream<gpu>::GetStream(s);
// run averagepool kernel
adaptiveaveragepool <<<blocks, threads, 0, stream>>> (
input_data, output_data, isizeH, isizeW, osizeH, osizeW,
istrideD, istrideH, istrideW);
MSHADOW_CUDA_POST_KERNEL_CHECK(AdaptiveAvgPoolUpdateOutput);
}
template<typename xpu, typename DType, typename AccReal>
void AdaptiveAvgPoolUpdateGradInput(mshadow::Stream<gpu> *s,
const std::vector<TBlob> &input,
const std::vector<TBlob> &output) {
Tensor<xpu, 4, DType> gradOut = input[0].get<xpu, 4, DType>(s);
Tensor<xpu, 4, DType> gradIn = output[0].get<xpu, 4, DType>(s);
DType *gradOutput_data = gradOut.dptr_;
DType *gradInput_data = gradIn.dptr_;
int64_t sizeB = gradIn.size(0);
int64_t sizeD = gradIn.size(1);
int64_t isizeH = gradIn.size(2);
int64_t isizeW = gradIn.size(3);
int64_t osizeH = gradOut.size(2);
int64_t osizeW = gradOut.size(3);
// cuda blocks & threads:
int blocksH = max(static_cast<int>(16L / sizeD), 1);
dim3 blocks(sizeB * sizeD, blocksH);
dim3 threads(32, 8);
cudaStream_t stream = mshadow::Stream<gpu>::GetStream(s);
// run updateGradInput kernel, accumulate gradients atomically
atomicadaptiveaveragegradinput <<<blocks, threads, 0, stream>>> (
gradInput_data, gradOutput_data, isizeH, isizeW, osizeH, osizeW);
MSHADOW_CUDA_POST_KERNEL_CHECK(AdaptiveAvgPoolUpdateGradInput);
}
NNVM_REGISTER_OP(_contrib_AdaptiveAvgPooling2D)
.set_attr<FCompute>("FCompute<gpu>", AdaptiveAvgPoolOpForward<gpu>);
NNVM_REGISTER_OP(_backward_contrib_AdaptiveAvgPooling2D)
.set_attr<FCompute>("FCompute<gpu>", AdaptiveAvgPoolOpBackward<gpu>);
} // namespace op
} // namespace mxnet