void operator()(OpKernelContext* ctx, const DepthwiseArgs& args,
                  const T* out_backprop, const T* input, T* filter_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));

    const int64_t filter_spatial_size = args.filter_rows * args.filter_cols;
    const int64_t padded_out_depth_size =
        ((args.out_depth + kPacketSize - 1) / kPacketSize) * kPacketSize;

    // Allocate output buffers for each image in 'batch' (padded to vector
    // register boundaries).
    Tensor output_buffer;
    OP_REQUIRES_OK(
        ctx, ctx->allocate_temp(DataTypeToEnum<T>::value,
                                TensorShape({args.batch, filter_spatial_size,
                                             padded_out_depth_size}),
                                &output_buffer));
    T* output_buffer_data = output_buffer.template flat<T>().data();

    // Computes one shard of depthwise conv2d backprop filter.
    auto shard = [&ctx, &args, &out_backprop, &input, &output_buffer_data](
                     int64_t start, int64_t limit) {
      static const int64_t kPacketSize = (sizeof(Packet) / sizeof(T));
      const int64_t filter_spatial_size = args.filter_rows * args.filter_cols;
      const int64_t padded_out_depth_size =
          ((args.out_depth + kPacketSize - 1) / kPacketSize) * kPacketSize;

      // Allocate buffer for local input regions.
      Tensor input_buffer;
      OP_REQUIRES_OK(
          ctx, ctx->allocate_temp(
                   DataTypeToEnum<T>::value,
                   TensorShape({filter_spatial_size, padded_out_depth_size}),
                   &input_buffer));
      T* input_buffer_data = input_buffer.template flat<T>().data();

      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 padded_filter_size =
          filter_spatial_size * padded_out_depth_size;

      for (int b = start; b < limit; ++b) {
        // Initialize 'output_buffer' for 'b'.
        auto* output_buffer = output_buffer_data + b * padded_filter_size;
        memset(output_buffer, 0, padded_filter_size * sizeof(T));

        for (int out_r = 0; out_r < args.out_rows; ++out_r) {
          for (int out_c = 0; out_c < args.out_cols; ++out_c) {
            // Populate 'input_buffer_data' with data from local input region.
            functor::DepthwiseInputCopyOp<T>()(
                args, padded_out_depth_size, out_r, out_c,
                input + b * input_image_size, input_buffer_data);
            // Compute depthwise backprop filter.
            ComputeBackpropFilter(args, padded_out_depth_size, out_r, out_c,
                                  out_backprop + b * output_image_size,
                                  input_buffer_data, output_buffer);
          }
        }
      }
    };
    const int64_t shard_cost = args.out_rows * args.out_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);

    // Accumulate 'output_buffer' from each shard into 'output'.
    const int64_t out_depth = args.out_depth;
    const int64_t vectorized_size = (out_depth / kPacketSize) * kPacketSize;
    const int64_t scalar_size = out_depth - vectorized_size;
    const int64_t padded_filter_size =
        filter_spatial_size * padded_out_depth_size;
    memset(filter_backprop, 0, filter_spatial_size * out_depth * sizeof(T));

    for (int64_t i = 0; i < filter_spatial_size; ++i) {
      const int64_t buffer_base = i * padded_out_depth_size;
      const int64_t output_base = i * out_depth;
      // Write vectorized length of filter's inner dimension to output.
      for (int64_t j = 0; j < vectorized_size; j += kPacketSize) {
        // Load data from 'filter_backprop' into vector register.
        auto out_block_data = filter_backprop + output_base + j;
        auto out_block = Eigen::internal::ploadu<Packet>(out_block_data);
        for (int b = 0; b < args.batch; ++b) {
          // Load data from 'output_buffer' for 'b'.
          const auto* output_buffer =
              output_buffer_data + b * padded_filter_size;
          const auto v =
              Eigen::internal::ploadu<Packet>(output_buffer + buffer_base + j);
          // Add 'v' to 'out_block'.
          out_block = Eigen::internal::padd<Packet>(out_block, v);
        }
        // Store 'out_block' back to memory.
        Eigen::internal::pstoreu<T>(out_block_data, out_block);
      }
      // Write scalar length of filter's inner dimension to output.
      for (int64_t j = 0; j < scalar_size; ++j) {
        for (int b = 0; b < args.batch; ++b) {
          const auto* output_buffer =
              output_buffer_data + b * padded_filter_size;
          filter_backprop[output_base + vectorized_size + j] +=
              output_buffer[buffer_base + vectorized_size + j];
        }
      }
    }
  }