/* Copyright 2017 ComputerGraphics Tuebingen. 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. ==============================================================================*/ //Authors: Fabian Groh, Patrick Wieschollek, Hendrik P.A. Lensch #if GOOGLE_CUDA #define EIGEN_USE_GPU #include #include "flex_conv_op.h" #include "tensorflow/core/util/cuda_kernel_helper.h" namespace FlexConvCuda { using CudaLaunchConfig = ::tensorflow::CudaLaunchConfig; constexpr __host__ __device__ int pmin(int x, int y) { return x <= y ? x : y; } template struct ForwardKernel; template __global__ void runForwardKernel( const ForwardKernel kernel) { kernel(); } template struct ForwardKernel { enum { PMIN = 3 // only for unrolling }; void launch(int B) { dim3 block(C_N); dim3 grid((N - 1) / C_N + 1, (Dout - 1) / C_Dout + 1, B); size_t shm_size = (Dp + 1) * C_Din * C_Dout * sizeof(Dtype); runForwardKernel<<>>((*this)); } __device__ __forceinline__ void operator()() const { extern __shared__ Dtype s_shm[]; Dtype* s_theta = (float*)&s_shm[0]; Dtype* s_bias = (float*)&s_shm[Dp * C_Din * C_Dout]; // glob ids int b = blockIdx.z; int n = blockIdx.x * C_N + threadIdx.x; Dtype result[C_Dout]; for (int dout = 0; dout < C_Dout; ++dout) { result[dout] = 0.0; } Dtype p0[Dp]; #pragma unroll pmin(Dp, PMIN) for (int dp = 0; dp < Dp && n < N; ++dp) { p0[dp] = d_positions[b * Dp * N + dp * N + n]; } for (int o_din = 0; o_din < Din; o_din += C_Din) { // load shm __syncthreads(); for (int tid = threadIdx.x; tid < Dp * C_Din * C_Dout; tid += C_N) { int dp = tid / (C_Din * C_Dout); int din = (tid % (C_Din * C_Dout)) / C_Dout; int dout = tid % C_Dout; int g_dout = (dout + blockIdx.y * C_Dout); int g_din = o_din + din; if (g_dout < Dout && g_din < Din) { s_theta[dp * C_Din * C_Dout + din * C_Dout + dout] = d_theta[dp * Din * Dout + g_din * Dout + g_dout]; if (!dp) s_bias[din * C_Dout + dout] = d_bias[g_din * Dout + g_dout]; } } __syncthreads(); if (n < N) { // Loop over K for (int k = 0; k < K && n < N; ++k) { NBtype nk = d_neighborhood[b * K * N + k * N + n]; Dtype q[Dp]; #pragma unroll pmin(Dp, PMIN) for (int dp = 0; dp < Dp; ++dp) { q[dp] = d_positions[b * Dp * N + dp * N + nk] - p0[dp]; } // Loop over Din for (int din = 0; din < C_Din && (o_din + din) < Din; ++din) { Dtype fk = d_features[b * Din * N + (o_din + din) * N + nk]; // Loop over partial Dout for (int dout = 0; dout < C_Dout && (dout + blockIdx.y * C_Dout) < Dout; ++dout) { Dtype w = 0.0; for (int dp = 0; dp < Dp; ++dp) w += q[dp] * s_theta[dp * C_Din * C_Dout + din * C_Dout + dout]; w += s_bias[din * C_Dout + dout]; result[dout] += w * fk; } } } } } for (int dout = 0; dout < C_Dout && (dout + blockIdx.y * C_Dout) < Dout && n < N; ++dout) { d_output[b * Dout * N + (dout + blockIdx.y * C_Dout) * N + n] = result[dout]; } } // features: incoming features [B, Din, N]. // position: each datapoint in nd space [B, Dp, N]. // neighborhood: all K nearest neighbors [B, K, N]. const Dtype* d_features; const Dtype* d_positions; const NBtype* d_neighborhood; // theta: parameters for kernel function [Dp, // Din, Dout]. bias: parameters for kernel function [Din, Dout]. const Dtype* d_theta; const Dtype* d_bias; // output: each feature description for each point [B, Dout, N]. Dtype* d_output; int N; int K; int Din; int Dout; }; template struct BackwardThetaKernel; template __global__ void runBackwardKernel(const BackwardThetaKernel kernel) { kernel(); } template struct BackwardThetaKernel { enum { C_N = 256, DP_MAX = 3, DEGREE_MAX = 2 }; void launch() { dim3 block(C_N); dim3 grid(Dout, Din); runBackwardKernel<<>>((*this)); } __device__ __forceinline__ void operator()() const { typedef cub::BlockReduce BlockReduce; __shared__ typename BlockReduce::TempStorage temp_storage; Dtype theta_diff[DP_MAX]; for (int dp = 0; dp < Dp; ++dp) theta_diff[dp] = 0; Dtype bias_diff = 0; int dout = blockIdx.x; int din = blockIdx.y; for (int b = 0; b < B; ++b) { for (int n = threadIdx.x; n < N; n += C_N) { Dtype topdiff = d_topdiff[b * Dout * N + dout * N + n]; for (int k = 0; k < K; ++k) { int nk0 = d_neigh[b * N * K + 0 * N + n]; int nk = d_neigh[b * N * K + k * N + n]; Dtype feature = d_features[b * Din * N + din * N + nk]; for (int dp = 0; dp < Dp; ++dp) { Dtype diffpos = d_pos[b * Dp * N + dp * N + nk] - d_pos[b * Dp * N + dp * N + nk0]; theta_diff[dp] += feature * diffpos * topdiff; } bias_diff += feature * topdiff; } } } for (int dp = 0; dp < Dp; ++dp) { // for (int dd = 0; dd < Ddegree; ++dd) { Dtype thread_data = theta_diff[dp]; Dtype aggregate = BlockReduce(temp_storage).Sum(thread_data, N); if (!threadIdx.x) { d_theta_out[dp * Din * Dout + din * Dout + dout] = aggregate; } // } __syncthreads(); } Dtype thread_data = bias_diff; Dtype aggregate = BlockReduce(temp_storage).Sum(thread_data, N); if (!threadIdx.x) d_bias_out[din * Dout + dout] = aggregate; } const Dtype* d_topdiff; const Dtype* d_pos; const Dtype* d_features; const int* d_neigh; const Dtype* d_theta; const Dtype* d_bias; Dtype* d_theta_out; Dtype* d_bias_out; int B; int N; int K; int Ddegree; int Dp; int Din; int Dout; }; template struct BackwardFeatureKernel; template __global__ void runBackwardKernel(const BackwardFeatureKernel kernel) { kernel(); } template struct BackwardFeatureKernel { enum { C_N = 32, C_Dout = 32, // multiple of Warpsize is better C_Din = 8 // reduce first }; void launch(int B) { dim3 fblock(C_N, C_Din); dim3 fgrid((N - 1) / C_N + 1, (Din - 1) / C_Din + 1, B); const int theta_size = Dp * C_Din * C_Dout; const int bias_size = C_Din * C_Dout; const int topdiff_size = C_N * C_Dout; const int pos_size = C_N * K * Dp; const int nk_size = C_N * K; int shm = (theta_size + bias_size + topdiff_size + pos_size) * sizeof(Dtype) + (nk_size) * sizeof(int); runBackwardKernel<<>>((*this)); } __device__ __forceinline__ void operator()() const { extern __shared__ float s_shm[]; int i_n = threadIdx.x; int i_din = threadIdx.y; int b = blockIdx.z; int n = blockIdx.x * C_N + i_n; int din = blockIdx.y * C_Din + i_din; Dtype* s_theta = (Dtype*)&s_shm[0]; Dtype* s_bias = (Dtype*)&s_theta[Dp * C_Din * C_Dout]; Dtype* s_topdiff = (Dtype*)&s_bias[C_Din * C_Dout]; Dtype* s_pos = (Dtype*)&s_topdiff[C_N * C_Dout]; int* s_nk = (int*)&s_pos[C_N * K * Dp]; for (int k = threadIdx.y; k < K && n < N; k += blockDim.y) { int nk = d_neigh[b * K * N + k * N + n]; s_nk[k * C_N + i_n] = nk; for (int i_dp = 0; i_dp < Dp; ++i_dp) { s_pos[k * C_N * Dp + i_dp * C_N + i_n] = d_pos[b * Dp * N + i_dp * N + nk]; } } __syncthreads(); for (int i_dp = 0; i_dp < Dp; ++i_dp) { Dtype val0 = s_pos[0 * C_N * Dp + i_dp * C_N + i_n]; __syncthreads(); for (int k = threadIdx.y; k < K && n < N; k += blockDim.y) { s_pos[k * C_N * Dp + i_dp * C_N + i_n] -= val0; } } for (int dout_outer = 0; dout_outer < (Dout - 1) / C_Dout + 1; ++dout_outer) { __syncthreads(); // fill s_theta int dout = dout_outer * C_Dout + i_n; if (din < Din && dout < Dout) { for (int i_dp = 0; i_dp < Dp; ++i_dp) s_theta[i_dp * C_Din * C_Dout + i_din * C_Dout + i_n] = d_theta[i_dp * Din * Dout + din * Dout + dout]; s_bias[i_din * C_Dout + i_n] = d_bias[din * Dout + dout]; } if (n < N) { for (int i_dout = threadIdx.y; i_dout < C_Dout && (dout_outer * C_Dout + i_dout) < Dout; i_dout += blockDim.y) s_topdiff[i_dout * C_N + i_n] = d_topdiff[b * Dout * N + (dout_outer * C_Dout + i_dout) * N + n]; } for (int dout_inner = 0; dout_inner < C_Dout && (dout_outer * C_Dout + dout_inner) < Dout; ++dout_inner) { for (int k = 0; k < K; k++) { __syncthreads(); if (n < N && din < Din) { Dtype W = 0; for (int dp = 0; dp < Dp; ++dp) { const Dtype diffpos = s_pos[k * C_N * Dp + dp * C_N + i_n]; W += s_theta[dp * C_Din * C_Dout + i_din * C_Dout + dout_inner] * diffpos; } W += s_bias[i_din * C_Dout + dout_inner]; Dtype value = W * s_topdiff[dout_inner * C_N + i_n]; atomicAdd( &d_features_out[b * Din * N + din * N + s_nk[k * C_N + i_n]], value); } } } } } const Dtype* d_topdiff; const Dtype* d_pos; const Dtype* d_features; const int* d_neigh; const Dtype* d_theta; const Dtype* d_bias; Dtype* d_features_out; int N; int K; int Dp; int Din; int Dout; }; } // namespace FlexConvCuda namespace tensorflow { namespace functor { template struct FlexConvFunctor { void operator()(::tensorflow::OpKernelContext* ctx, const Tensor& features, const Tensor& theta, const Tensor& bias, const Tensor& neighborhood, const Tensor& positions, Tensor* output) { typedef int NBtype; const int B = neighborhood.dim_size(0); const int K = neighborhood.dim_size(1); const int N = neighborhood.dim_size(2); const int Dp = theta.dim_size(1); const int Din = theta.dim_size(2); const int Dout = theta.dim_size(3); FlexConvCuda::ForwardKernel fwk; fwk.N = N; fwk.K = K; fwk.Din = Din; fwk.Dout = Dout; fwk.d_features = features.flat().data(); fwk.d_positions = positions.flat().data(); fwk.d_neighborhood = neighborhood.flat().data(); fwk.d_theta = theta.flat().data(); fwk.d_bias = bias.flat().data(); fwk.d_output = output->flat().data(); fwk.launch(B); if (!ctx->eigen_gpu_device().ok()) { ctx->SetStatus(tensorflow::errors::Internal( "FlexConvInvFunctor::forward::ForwardKernel execution failed")); } } }; template struct FlexConvFunctor; template struct FlexConvGrad { void operator()(::tensorflow::OpKernelContext* ctx, const Tensor& features_, const Tensor& theta_, const Tensor& bias_, const Tensor& neighborhood_, const Tensor& positions_, const Tensor& topdiff_, Tensor* grad_features_, Tensor* grad_theta_, Tensor* grad_bias_) { const auto features = features_.tensor(); const auto theta = theta_.tensor(); const auto bias = bias_.tensor(); const auto neighborhood = neighborhood_.tensor(); const auto positions = positions_.tensor(); const auto topdiff = topdiff_.tensor(); auto grad_features = grad_features_->tensor(); auto grad_theta = grad_theta_->tensor(); auto grad_bias = grad_bias_->tensor(); // get dimensions const int B = neighborhood_.dim_size(0); const int K = neighborhood_.dim_size(1); const int N = neighborhood_.dim_size(2); const int Dp = theta_.dim_size(1); const int Din = theta_.dim_size(2); const int Dout = theta_.dim_size(3); const int* neighborhood_ptr = reinterpret_cast(neighborhood.data()); const Dtype* positions_ptr = reinterpret_cast(positions.data()); const Dtype* features_ptr = reinterpret_cast(features.data()); const Dtype* theta_ptr = reinterpret_cast(theta.data()); const Dtype* bias_ptr = reinterpret_cast(bias.data()); const Dtype* topdiff_ptr = reinterpret_cast(topdiff.data()); Dtype* grad_features_ptr = reinterpret_cast(grad_features.data()); Dtype* grad_theta_ptr = reinterpret_cast(grad_theta.data()); Dtype* grad_bias_ptr = reinterpret_cast(grad_bias.data()); cudaMemset(grad_features_ptr, 0, B * Din * N * sizeof(Dtype)); ::tensorflow::CudaLaunchConfig cfg = ::tensorflow::GetCudaLaunchConfig(N, ctx->eigen_device()); typedef FlexConvCuda::BackwardFeatureKernel BFK; BFK bfk; bfk.N = N; bfk.K = K; bfk.Dp = Dp; bfk.Din = Din; bfk.Dout = Dout; bfk.d_pos = positions_ptr; bfk.d_neigh = neighborhood_ptr; bfk.d_features = features_ptr; bfk.d_theta = theta_ptr; bfk.d_bias = bias_ptr; bfk.d_topdiff = topdiff_ptr; bfk.d_features_out = grad_features_ptr; bfk.launch(B); if (!ctx->eigen_gpu_device().ok()) { ctx->SetStatus( tensorflow::errors::Internal("CUDA: BackwardFeatureKernel Error!\n")); } typedef FlexConvCuda::BackwardThetaKernel BTK; BTK btk; btk.B = B; btk.N = N; btk.K = K; btk.Dp = Dp; btk.Din = Din; btk.Dout = Dout; btk.d_pos = positions_ptr; btk.d_neigh = neighborhood_ptr; btk.d_features = features_ptr; btk.d_theta = theta_ptr; btk.d_bias = bias_ptr; btk.d_topdiff = topdiff_ptr; btk.d_theta_out = grad_theta_ptr; btk.d_bias_out = grad_bias_ptr; btk.launch(); if (!ctx->eigen_gpu_device().ok()) { ctx->SetStatus( tensorflow::errors::Internal("CUDA: BackwardThetaKernel Error!\n")); } } }; template struct FlexConvGrad; } // namespace functor } // namespace tensorflow #endif // GOOGLE_CUDA