void operator()(OpKernelContext* ctx, const DepthwiseArgs& args,
                  const T* out_backprop, const T* depthwise_filter,
                  T* in_backprop, TensorFormat data_format) {
    OP_REQUIRES(
        ctx, data_format == FORMAT_NHWC,
        errors::Unimplemented(
            "Depthwise convolution on CPU is only supported for NHWC format"));

    static const int64_t kPacketSize = (sizeof(Packet) / sizeof(T));

    // Pad 'depthwise_filter' to vector register width (if needed).
    const bool pad_filter = (args.out_depth % kPacketSize) == 0 ? false : true;
    Tensor padded_filter;
    if (pad_filter) {
      // Allocate space for padded filter.
      const int64_t filter_spatial_size = args.filter_rows * args.filter_cols;
      const int64_t padded_filter_inner_dim_size =
          ((args.out_depth + kPacketSize - 1) / kPacketSize) * kPacketSize;
      OP_REQUIRES_OK(
          ctx, ctx->allocate_temp(DataTypeToEnum<T>::value,
                                  TensorShape({filter_spatial_size,
                                               padded_filter_inner_dim_size}),
                                  &padded_filter));
      // Write out padded filter.
      functor::DepthwiseFilterPadOp<T>()(
          args, depthwise_filter, padded_filter.template flat<T>().data());
    }
    const T* filter_data =
        pad_filter ? padded_filter.template flat<T>().data() : depthwise_filter;

    // Computes one shard of depthwise conv2d backprop input.
    auto shard = [&ctx, &args, &out_backprop, &filter_data, &in_backprop](
                     int64_t start, int64_t limit) {
      static const int64_t kPacketSize = (sizeof(Packet) / sizeof(T));

      const int64_t input_image_size =
          args.in_rows * args.in_cols * args.in_depth;
      const int64_t output_image_size =
          args.out_rows * args.out_cols * args.out_depth;
      const int64_t filter_spatial_size = args.filter_rows * args.filter_cols;
      const int64_t padded_filter_inner_dim_size =
          ((args.out_depth + kPacketSize - 1) / kPacketSize) * kPacketSize;

      // Allocate buffer to copy regions from 'out_backprop'.
      Tensor out_bprop_buffer;
      OP_REQUIRES_OK(
          ctx, ctx->allocate_temp(DataTypeToEnum<T>::value,
                                  TensorShape({filter_spatial_size,
                                               padded_filter_inner_dim_size}),
                                  &out_bprop_buffer));
      T* out_bprop_buf = out_bprop_buffer.template flat<T>().data();

      // Allocate buffer for intermediate results.
      Tensor in_bprop_buffer;
      OP_REQUIRES_OK(
          ctx, ctx->allocate_temp(DataTypeToEnum<T>::value,
                                  TensorShape({padded_filter_inner_dim_size}),
                                  &in_bprop_buffer));
      T* in_bprop_buf = in_bprop_buffer.template flat<T>().data();

      for (int64_t b = start; b < limit; ++b) {
        for (int64_t in_r = 0; in_r < args.in_rows; ++in_r) {
          for (int64_t in_c = 0; in_c < args.in_cols; ++in_c) {
            // Populate 'out_bprop_buf' from local 'out_backprop' region.
            CopyOutputBackpropRegion<T>(
                args, padded_filter_inner_dim_size, in_r, in_c,
                out_backprop + b * output_image_size, out_bprop_buf);

            // Compute depthwise backprop input.
            ComputeBackpropInput<T>(args, padded_filter_inner_dim_size, in_r,
                                    in_c, filter_data, out_bprop_buf,
                                    in_bprop_buf,
                                    in_backprop + b * input_image_size);
          }
        }
      }
    };

    const int64_t shard_cost = args.in_rows * args.in_cols * args.out_depth;
    auto worker_threads = *(ctx->device()->tensorflow_cpu_worker_threads());
    Shard(worker_threads.num_threads, worker_threads.workers, args.batch,
          shard_cost, shard);
  }