constexpr char kConv2dBackpropFilter[] = "Conv2DBackpropFilter";
constexpr char kConv2dBackpropInput[] = "Conv2DBackpropInput";
constexpr char kFusedConv2dBiasActivation[] = "FusedConv2DBiasActivation";
+constexpr char kDepthwiseConv2dNative[] = "DepthwiseConv2dNative";
+constexpr char kDepthwiseConv2dNativeBackpropFilter[] =
+ "DepthwiseConv2dNativeBackpropFilter";
+constexpr char kDepthwiseConv2dNativeBackpropInput[] =
+ "DepthwiseConv2dNativeBackpropInput";
constexpr char kMatMul[] = "MatMul";
constexpr char kSparseMatMul[] = "SparseMatMul";
constexpr char kPlaceholder[] = "Placeholder";
wrap(&OpLevelCostEstimator::PredictConv2DBackpropInput)},
{kFusedConv2dBiasActivation,
wrap(&OpLevelCostEstimator::PredictFusedConv2DBiasActivation)},
+ // reuse Conv2D for DepthwiseConv2dNative because the caculation is the
+ // same although the actual meaning of the parameters are different. See
+ // comments in PredictConv2D and related functions
+ {kDepthwiseConv2dNative, wrap(&OpLevelCostEstimator::PredictConv2D)},
+ {kDepthwiseConv2dNativeBackpropFilter,
+ wrap(&OpLevelCostEstimator::PredictConv2DBackpropFilter)},
+ {kDepthwiseConv2dNativeBackpropInput,
+ wrap(&OpLevelCostEstimator::PredictConv2DBackpropInput)},
{kMatMul, wrap(&OpLevelCostEstimator::PredictMatMul)},
{kSparseMatMul, wrap(&OpLevelCostEstimator::PredictMatMul)},
{kBatchMatMul, wrap(&OpLevelCostEstimator::PredictBatchMatMul)},
int64 OpLevelCostEstimator::CountConv2DOperations(
const OpInfo& op_features, ConvolutionDimensions* conv_info,
bool* found_unknown_shapes) const {
- if (op_features.op() != kConv2d) {
- LOG(ERROR) << "Invalid Operation";
- return 0;
- }
+ DCHECK(op_features.op() == kConv2d ||
+ op_features.op() == kDepthwiseConv2dNative)
+ << "Invalid Operation: not Conv2D nor DepthwiseConv2dNative";
+
ConvolutionDimensions conv_dims = ConvolutionDimensionsFromInputs(
op_features.inputs(0).shape(), op_features.inputs(1).shape(), op_features,
found_unknown_shapes);
+ // in DepthwiseConv2dNative conv_dims.oz is actually the channel depth
+ // multiplier; The effective output channel depth oz_effective is
+ // conv_dims.iz * conv_dims.oz. thus # ops = N x H x W x oz_effective x 2RS.
+ // Compare to Conv2D where # ops = N x H x W x iz x oz x 2RS,
+ // oz = oz_effective, then Conv2D_ops / Depthwise_conv2d_native_ops = iz.
int64 ops = conv_dims.batch;
ops *= conv_dims.ox * conv_dims.oy;
ops *= conv_dims.kx * conv_dims.ky;
- ops *= conv_dims.iz * conv_dims.oz;
+ if (op_features.op() == kConv2d) {
+ ops *= conv_dims.iz * conv_dims.oz;
+ } else {
+ // To ensure output tensor dims to be correct for DepthwiseConv2DNative,
+ // although ops are the same as Conv2D.
+ conv_dims.oz *= conv_dims.iz;
+ ops *= conv_dims.oz;
+ }
ops *= kOpsPerMac;
if (conv_info != nullptr) {
bool* found_unknown_shapes) const {
int64 ops = 0;
- DCHECK_EQ(kConv2dBackpropInput, op_features.op());
+ DCHECK(op_features.op() == kConv2dBackpropInput ||
+ op_features.op() == kDepthwiseConv2dNativeBackpropInput)
+ << "Invalid Operation: not kConv2dBackpropInput nor"
+ "kDepthwiseConv2dNativeBackpropInput";
if (op_features.inputs_size() < 2) {
*found_unknown_shapes = true;
ops = conv_dims.batch;
ops *= conv_dims.ox * conv_dims.oy;
ops *= conv_dims.kx * conv_dims.ky;
- ops *= conv_dims.iz * conv_dims.oz;
- ops *= kOpsPerMac;
+ if (op_features.op() == kConv2dBackpropInput) {
+ ops *= conv_dims.iz * conv_dims.oz;
+ } else {
+ // conv_dims always use forward path definition regardless
+ conv_dims.oz *= conv_dims.iz;
+ ops *= conv_dims.oz;
+ }
- VLOG(1) << "Operations for Conv2DBackpropInput " << ops;
+ VLOG(1) << "Operations for" << op_features.op() << " " << ops;
if (returned_conv_dims != nullptr) {
*returned_conv_dims = conv_dims;
const OpInfo& op_features, ConvolutionDimensions* returned_conv_dims,
bool* found_unknown_shapes) const {
int64 ops = 0;
- DCHECK_EQ(kConv2dBackpropFilter, op_features.op());
+
+ DCHECK(op_features.op() == kConv2dBackpropFilter ||
+ op_features.op() == kDepthwiseConv2dNativeBackpropFilter)
+ << "Invalid Operation: not kConv2dBackpropFilter nor"
+ "kDepthwiseConv2dNativeBackpropFilter";
TensorShapeProto filter_shape;
bool shape_found = false;
ops = conv_dims.batch;
ops *= conv_dims.ox * conv_dims.oy;
ops *= conv_dims.kx * conv_dims.ky;
- ops *= conv_dims.iz * conv_dims.oz;
- ops *= kOpsPerMac;
+ if (op_features.op() == kConv2dBackpropFilter) {
+ ops *= conv_dims.iz * conv_dims.oz;
+ } else {
+ // conv_dims always use forward path definition regardless
+ conv_dims.oz *= conv_dims.iz;
+ ops *= conv_dims.oz;
+ }
- VLOG(1) << "Operations for Conv2DBackpropFilter" << ops;
+ VLOG(1) << "Operations for" << op_features.op() << " " << ops;
if (returned_conv_dims != nullptr) {
*returned_conv_dims = conv_dims;
return op_context;
}
+// Describe DepthwiseConvolution constructs an OpContext for a
+// DepthwiseConv2dNative applied to an input
+// tensor with shape (batch, ix, iy, iz1) and a kernel tensor with shape
+// (kx, ky, iz2, cm). cm is channel multiplier
+
+OpContext DescribeDepthwiseConv2dNative(int batch, int ix, int iy, int iz1,
+ int iz2, int kx, int ky, int cm) {
+ OpContext op_context;
+ SetCpuDevice(&op_context.op_info);
+ op_context.op_info.set_op("DepthwiseConv2dNative");
+
+ DescribeTensor4D(batch, ix, iy, iz1, op_context.op_info.add_inputs());
+ DescribeTensor4D(kx, ky, iz2, cm, op_context.op_info.add_inputs());
+
+ return op_context;
+}
+
// DescribeFusedConv2DBiasActivation constructs an OpContext for a
// FusedConv2DBiasActivation applied to a convolution input tensor with shape
// (batch, ix, iy, iz1), a kernel tensor with shape (kx, ky, iz2, oz), a
EXPECT_FALSE(cost.inaccurate);
}
+TEST_F(OpLevelCostEstimatorTest, DepthwiseConv2dNativeExecutionTime) {
+ auto cost =
+ PredictCosts(DescribeDepthwiseConv2dNative(16, 19, 19, 48, 48, 5, 5, 3));
+ EXPECT_EQ(Costs::Duration(112340), cost.memory_time);
+ EXPECT_EQ(Costs::Duration(4158720), cost.compute_time);
+ EXPECT_EQ(Costs::Duration(4271060), cost.execution_time);
+ EXPECT_FALSE(cost.inaccurate);
+}
+
TEST_F(OpLevelCostEstimatorTest, DummyExecutionTime) {
auto cost = PredictCosts(DescribeBinaryOp("Dummy", 1000, 1));
EXPECT_EQ(Costs::Duration(2000), cost.memory_time);