// Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. // // NVIDIA CORPORATION and its licensors retain all intellectual property // and proprietary rights in and to this software, related documentation // and any modifications thereto. Any use, reproduction, disclosure or // distribution of this software and related documentation without an express // license agreement from NVIDIA CORPORATION is strictly prohibited. //------------------------------------------------------------------------ // Common op attribute parser. static __host__ void interpolateParseOpAttributes(OpKernelConstruction* ctx, InterpolateKernelParams& p, bool enableDA) { if (enableDA) { OP_REQUIRES_OK(ctx, ctx->GetAttr("diff_attrs_all", &p.diff_attrs_all)); if (!p.diff_attrs_all) { std::vector diff_attrs_vec; OP_REQUIRES_OK(ctx, ctx->GetAttr("diff_attrs", &diff_attrs_vec)); OP_REQUIRES(ctx, diff_attrs_vec.size() > 0, errors::InvalidArgument("differentiation enabled with empty diff_attrs list")); OP_REQUIRES(ctx, diff_attrs_vec.size() <= IP_MAX_DIFF_ATTRS, errors::InvalidArgument("too many entries in diff_attrs list (increase IP_MAX_DIFF_ATTRS)")); p.numDiffAttr = diff_attrs_vec.size(); memcpy(p.diffAttrs, &diff_attrs_vec[0], diff_attrs_vec.size()*sizeof(int)); } } } //------------------------------------------------------------------------ // Forward TensorFlow op. template struct InterpolateFwdOp : public OpKernel { InterpolateKernelParams m_attribs; InterpolateFwdOp(OpKernelConstruction* ctx): OpKernel(ctx) { memset(&m_attribs, 0, sizeof(m_attribs)); interpolateParseOpAttributes(ctx, m_attribs, ENABLE_DA); } void Compute(OpKernelContext* ctx) { InterpolateKernelParams& p = m_attribs; cudaStream_t stream = ctx->eigen_device().stream(); // Get input. const Tensor& attr = ctx->input(0); const Tensor& rast = ctx->input(1); const Tensor& tri = ctx->input(2); const Tensor& rast_db = ctx->input(ENABLE_DA ? 3 : 2); // Instance rendering mode? p.instance_mode = attr.dims() > 2; // Extract input dimensions. if (p.instance_mode) { p.numVertices = (attr.dims() > 1) ? attr.dim_size(1) : 0; p.numAttr = (attr.dims() > 2) ? attr.dim_size(2) : 0; } else { p.numVertices = (attr.dims() > 0) ? attr.dim_size(0) : 0; p.numAttr = (attr.dims() > 1) ? attr.dim_size(1) : 0; } p.numTriangles = (tri.dims() > 0) ? tri.dim_size(0) : 0; p.height = (rast.dims() > 1) ? rast.dim_size(1) : 0; p.width = (rast.dims() > 2) ? rast.dim_size(2) : 0; p.depth = (rast.dims() > 0) ? rast.dim_size(0) : 0; // Sanity checks. OP_REQUIRES(ctx, rast.dims() == 4 && rast.dim_size(0) > 0 && rast.dim_size(1) > 0 && rast.dim_size(2) > 0 && rast.dim_size(3) == 4, errors::InvalidArgument("rast must have shape[>0, >0, >0, 4]")); OP_REQUIRES(ctx, tri.dims() == 2 && tri.dim_size(0) > 0 && tri.dim_size(1) == 3, errors::InvalidArgument("tri must have shape [>0, 3]")); OP_REQUIRES(ctx, (attr.dims() == 2 || attr.dims() == 3) && attr.dim_size(0) > 0 && attr.dim_size(1) > 0 && (attr.dims() == 2 || attr.dim_size(2) > 0), errors::InvalidArgument("attr must have shape [>0, >0, >0] or [>0, >0]")); if (p.instance_mode) OP_REQUIRES(ctx, attr.dim_size(0) == p.depth || attr.dim_size(0) == 1, errors::InvalidArgument("minibatch size mismatch between inputs rast, attr")); if (ENABLE_DA) { OP_REQUIRES(ctx, rast_db.dims() == 4 && rast_db.dim_size(0) > 0 && rast_db.dim_size(1) > 0 && rast_db.dim_size(2) > 0 && rast_db.dim_size(3) == 4, errors::InvalidArgument("rast_db must have shape[>0, >0, >0, 4]")); OP_REQUIRES(ctx, rast_db.dim_size(1) == rast.dim_size(1) && rast_db.dim_size(2) == rast.dim_size(2), errors::InvalidArgument("spatial size mismatch between inputs rast and rast_db")); OP_REQUIRES(ctx, rast_db.dim_size(0) == p.depth, errors::InvalidArgument("minibatch size mismatch between inputs rast, rast_db")); } // All diff attrs mode. if (p.diff_attrs_all) p.numDiffAttr = p.numAttr; // Get input pointers. p.attr = attr.flat().data(); p.rast = rast.flat().data(); p.tri = tri.flat().data(); p.attrBC = (p.instance_mode && attr.dim_size(0) == 1) ? 1 : 0; p.rastDB = ENABLE_DA ? rast_db.flat().data() : 0; // Allocate main output tensor. Tensor* out_tensor = NULL; TensorShape out_shape; out_shape.AddDim(p.depth); out_shape.AddDim(p.height); out_shape.AddDim(p.width); out_shape.AddDim(p.numAttr); OP_REQUIRES_OK(ctx, ctx->allocate_output(0, out_shape, &out_tensor)); p.out = out_tensor->flat().data(); // Allocate pixel differential output tensor. Tensor* out_da_tensor = NULL; out_shape.set_dim(3, p.numDiffAttr * 2); OP_REQUIRES_OK(ctx, ctx->allocate_output(1, out_shape, &out_da_tensor)); p.outDA = ENABLE_DA ? out_da_tensor->flat().data() : 0; // Verify that buffers are aligned to allow float2/float4 operations. OP_REQUIRES(ctx, !((uintptr_t)p.rast & 15), errors::Internal("rast input tensor not aligned to float4")); OP_REQUIRES(ctx, !((uintptr_t)p.rastDB & 15), errors::Internal("rast_db input tensor not aligned to float4")); if (ENABLE_DA) OP_REQUIRES(ctx, !((uintptr_t)p.outDA & 7), errors::Internal("out_da output tensor not aligned to float2")); // Choose launch parameters. dim3 blockSize = getLaunchBlockSize(IP_FWD_MAX_KERNEL_BLOCK_WIDTH, IP_FWD_MAX_KERNEL_BLOCK_HEIGHT, p.width, p.height); dim3 gridSize = getLaunchGridSize(blockSize, p.width, p.height, p.depth); // Launch CUDA kernel. void* args[] = {&p}; void* func = ENABLE_DA ? (void*)InterpolateFwdKernelDa : (void*)InterpolateFwdKernel; OP_CHECK_CUDA_ERROR(ctx, cudaLaunchKernel(func, gridSize, blockSize, args, 0, stream)); } }; REGISTER_OP("InterpolateFwd") .Input ("attr: float") .Input ("rast: float") .Input ("tri: int32") .Output ("out: float") .Output ("out_da: float"); REGISTER_OP("InterpolateFwdDa") .Input ("attr: float") .Input ("rast: float") .Input ("tri: int32") .Input ("rast_db: float") .Output ("out: float") .Output ("out_da: float") .Attr ("diff_attrs_all: int") .Attr ("diff_attrs: list(int)"); REGISTER_KERNEL_BUILDER(Name("InterpolateFwd") .Device(DEVICE_GPU), InterpolateFwdOp); REGISTER_KERNEL_BUILDER(Name("InterpolateFwdDa").Device(DEVICE_GPU), InterpolateFwdOp); //------------------------------------------------------------------------ // Gradient TensorFlow op. template struct InterpolateGradOp : public OpKernel { InterpolateKernelParams m_attribs; InterpolateGradOp(OpKernelConstruction* ctx): OpKernel(ctx) { memset(&m_attribs, 0, sizeof(m_attribs)); interpolateParseOpAttributes(ctx, m_attribs, ENABLE_DA); } void Compute(OpKernelContext* ctx) { InterpolateKernelParams& p = m_attribs; cudaStream_t stream = ctx->eigen_device().stream(); // Get input. const Tensor& attr = ctx->input(0); const Tensor& rast = ctx->input(1); const Tensor& tri = ctx->input(2); const Tensor& dy = ctx->input(3); const Tensor& rast_db = ctx->input(ENABLE_DA ? 4 : 3); const Tensor& dda = ctx->input(ENABLE_DA ? 5 : 3); // Instance rendering mode? p.instance_mode = attr.dims() > 2; // Extract input dimensions. if (p.instance_mode) { p.numVertices = (attr.dims() > 1) ? attr.dim_size(1) : 0; p.numAttr = (attr.dims() > 2) ? attr.dim_size(2) : 0; } else { p.numVertices = (attr.dims() > 0) ? attr.dim_size(0) : 0; p.numAttr = (attr.dims() > 1) ? attr.dim_size(1) : 0; } p.numTriangles = (tri.dims() > 0) ? tri.dim_size(0) : 0; p.depth = (rast.dims() > 0) ? rast.dim_size(0) : 0; p.height = (rast.dims() > 1) ? rast.dim_size(1) : 0; p.width = (rast.dims() > 2) ? rast.dim_size(2) : 0; int attr_depth = p.instance_mode ? (attr.dims() > 1 ? attr.dim_size(0) : 0) : 1; // Sanity checks. OP_REQUIRES(ctx, rast.dims() == 4 && rast.dim_size(0) > 0 && rast.dim_size(1) > 0 && rast.dim_size(2) > 0 && rast.dim_size(3) == 4, errors::InvalidArgument("rast must have shape[>0, >0, >0, 4]")); OP_REQUIRES(ctx, tri.dims() == 2 && tri.dim_size(0) > 0 && tri.dim_size(1) == 3, errors::InvalidArgument("tri must have shape [>0, 3]")); OP_REQUIRES(ctx, (attr.dims() == 2 || attr.dims() == 3) && attr.dim_size(0) > 0 && attr.dim_size(1) > 0 && (attr.dims() == 2 || attr.dim_size(2) > 0), errors::InvalidArgument("attr must have shape [>0, >0, >0] or [>0, >0]")); OP_REQUIRES(ctx, dy.dims() == 4 && dy.dim_size(0) > 0 && dy.dim_size(1) == p.height && dy.dim_size(2) == p.width && dy.dim_size(3) > 0, errors::InvalidArgument("dy must have shape [>0, height, width, >0]")); OP_REQUIRES(ctx, dy.dim_size(3) == p.numAttr, errors::InvalidArgument("argument count mismatch between inputs dy, attr")); OP_REQUIRES(ctx, (attr_depth == p.depth || attr_depth == 1) && dy.dim_size(0) == p.depth, errors::InvalidArgument("minibatch size mismatch between inputs rast, dy, attr")); if (ENABLE_DA) { OP_REQUIRES(ctx, dda.dims() == 4 && dda.dim_size(0) > 0 && dda.dim_size(1) == p.height && dda.dim_size(2) == p.width, errors::InvalidArgument("dda must have shape [>0, height, width, ?]")); OP_REQUIRES(ctx, dda.dim_size(0) == p.depth, errors::InvalidArgument("minibatch size mismatch between rast, dda")); } // All diff attrs mode. if (p.diff_attrs_all) p.numDiffAttr = p.numAttr; // Get input pointers. p.attr = attr.flat().data(); p.rast = rast.flat().data(); p.tri = tri.flat().data(); p.dy = dy.flat().data(); p.rastDB = ENABLE_DA ? rast_db.flat().data() : 0; p.dda = ENABLE_DA ? dda.flat().data() : 0; p.attrBC = (p.instance_mode && attr_depth < p.depth) ? 1 : 0; // Allocate attribute gradient output tensor. Tensor* grad_attr_tensor = NULL; TensorShape grad_attr_shape; if (p.instance_mode) grad_attr_shape.AddDim(attr_depth); grad_attr_shape.AddDim(p.numVertices); grad_attr_shape.AddDim(p.numAttr); OP_REQUIRES_OK(ctx, ctx->allocate_output(0, grad_attr_shape, &grad_attr_tensor)); p.gradAttr = grad_attr_tensor->flat().data(); // Allocate bary gradient output tensor. Tensor* grad_rast_tensor = NULL; TensorShape grad_rast_shape; grad_rast_shape.AddDim(p.depth); grad_rast_shape.AddDim(p.height); grad_rast_shape.AddDim(p.width); grad_rast_shape.AddDim(4); OP_REQUIRES_OK(ctx, ctx->allocate_output(1, grad_rast_shape, &grad_rast_tensor)); p.gradRaster = grad_rast_tensor->flat().data(); // Allocate bary pixel diff gradient output tensor. if (ENABLE_DA) { Tensor* grad_rast_db_tensor = NULL; OP_REQUIRES_OK(ctx, ctx->allocate_output(2, grad_rast_shape, &grad_rast_db_tensor)); p.gradRasterDB = grad_rast_db_tensor->flat().data(); } // Clear attribute gradients. cudaMemsetAsync(p.gradAttr, 0, attr_depth * p.numVertices * p.numAttr * sizeof(float), stream); // Verify that buffers are aligned to allow float2/float4 operations. OP_REQUIRES(ctx, !((uintptr_t)p.rast & 15), errors::Internal("rast input tensor not aligned to float4")); OP_REQUIRES(ctx, !((uintptr_t)p.gradRaster & 15), errors::Internal("grad_rast output tensor not aligned to float4")); if (ENABLE_DA) { OP_REQUIRES(ctx, !((uintptr_t)p.dda & 7), errors::Internal("dda input tensor not aligned to float2")); OP_REQUIRES(ctx, !((uintptr_t)p.rastDB & 15), errors::Internal("rast_db input tensor not aligned to float4")); OP_REQUIRES(ctx, !((uintptr_t)p.gradRasterDB & 15), errors::Internal("grad_rast_db output tensor not aligned to float4")); } // Choose launch parameters. dim3 blockSize = getLaunchBlockSize(IP_GRAD_MAX_KERNEL_BLOCK_WIDTH, IP_GRAD_MAX_KERNEL_BLOCK_HEIGHT, p.width, p.height); dim3 gridSize = getLaunchGridSize(blockSize, p.width, p.height, p.depth); // Launch CUDA kernel. void* args[] = {&p}; void* func = ENABLE_DA ? (void*)InterpolateGradKernelDa : (void*)InterpolateGradKernel; OP_CHECK_CUDA_ERROR(ctx, cudaLaunchKernel(func, gridSize, blockSize, args, 0, stream)); } }; REGISTER_OP("InterpolateGrad") .Input ("attr: float") .Input ("rast: float") .Input ("tri: int32") .Input ("dy: float") .Output ("grad_attr: float") .Output ("grad_rast: float") ; REGISTER_OP("InterpolateGradDa") .Input ("attr: float") .Input ("rast: float") .Input ("tri: int32") .Input ("dy: float") .Input ("rast_db: float") .Input ("dda: float") .Output ("grad_attr: float") .Output ("grad_rast: float") .Output ("grad_rast_db: float") .Attr ("diff_attrs_all: int") .Attr ("diff_attrs: list(int)"); ; REGISTER_KERNEL_BUILDER(Name("InterpolateGrad") .Device(DEVICE_GPU), InterpolateGradOp); REGISTER_KERNEL_BUILDER(Name("InterpolateGradDa").Device(DEVICE_GPU), InterpolateGradOp); //------------------------------------------------------------------------