  void Compute(OpKernelContext* const context) override {
    // node_ids.
    const Tensor* node_ids_t;
    OP_REQUIRES_OK(context, context->input("node_ids", &node_ids_t));
    const auto node_ids = node_ids_t->vec<int32>();

    // gradients.
    const Tensor* gradients_t;
    OP_REQUIRES_OK(context, context->input("gradients", &gradients_t));
    const auto gradients = gradients_t->matrix<float>();

    OP_REQUIRES(
        context, node_ids.size() == gradients.dimension(0),
        errors::InvalidArgument(
            "node_ids size should match 0th dim of gradients. node ids "
            "size: ",
            node_ids.size(), ", gradients dim0: ", gradients.dimension(0)));

    // hessians.
    const Tensor* hessians_t;
    OP_REQUIRES_OK(context, context->input("hessians", &hessians_t));
    const auto hessians = hessians_t->matrix<float>();

    // feature.
    const Tensor* feature_t;
    OP_REQUIRES_OK(context, context->input("feature", &feature_t));
    const auto feature = feature_t->matrix<int32>();

    // Infer batch size, feature dimension and stats dimension.
    const int64_t batch_size = node_ids_t->dim_size(0);
    const int64_t logits_dims = gradients_t->dim_size(1);
    const int64_t hessians_dims = hessians_t->dim_size(1);
    const int64_t stats_dims = logits_dims + hessians_dims;
    const int64_t feature_dims = feature_t->dim_size(1);

    // Allocate temporary stats tensor (Rank 4), upcasting to double.
    // A default bucket is added to the end for missing/default values.
    Tensor temp_stats_double_t;
    OP_REQUIRES_OK(
        context, context->allocate_temp(
                     DT_DOUBLE,
                     {max_splits_, feature_dims, num_buckets_ + 1, stats_dims},
                     &temp_stats_double_t));
    auto temp_stats_double = temp_stats_double_t.tensor<double, 4>();
    temp_stats_double.setZero();

    for (int i = 0; i < batch_size; ++i) {
      const int32_t node = node_ids(i);
      OP_REQUIRES(context, node >= 0,
                  errors::InvalidArgument(
                      "node_ids ", i, "th entry should be >=0, got: ", node));
      for (int feature_dim = 0; feature_dim < feature_dims; ++feature_dim) {
        const int32_t feature_value = feature(i, feature_dim);
        const int32_t bucket =
            (feature_value == -1) ? num_buckets_ : feature_value;
        for (int stat_dim = 0; stat_dim < logits_dims; ++stat_dim) {
          temp_stats_double(node, feature_dim, bucket, stat_dim) +=
              gradients(i, stat_dim);
        }
        for (int stat_dim = logits_dims; stat_dim < stats_dims; ++stat_dim) {
          temp_stats_double(node, feature_dim, bucket, stat_dim) +=
              hessians(i, stat_dim - logits_dims);
        }
      }
    }

    // Copy temp tensor over to output tensor, downcasting to float.
    Tensor* output_stats_summary_t = nullptr;
    OP_REQUIRES_OK(context, context->allocate_output(
                                "stats_summary", temp_stats_double_t.shape(),
                                &output_stats_summary_t));
    output_stats_summary_t->tensor<float, 4>() =
        temp_stats_double.template cast<float>();
  }
  void Compute(OpKernelContext* const context) override {
     
    const Tensor* node_ids_t;
    OP_REQUIRES_OK(context, context->input("node_ids", &node_ids_t));
    const auto node_ids = node_ids_t->vec<int32>();

     
    const Tensor* gradients_t;
    OP_REQUIRES_OK(context, context->input("gradients", &gradients_t));
    const auto gradients = gradients_t->matrix<float>();

    OP_REQUIRES(
        context, node_ids.size() == gradients.dimension(0),
        errors::InvalidArgument(
            "node_ids size should match 0th dim of gradients. node ids "
            "size: ",
            node_ids.size(), ", gradients dim0: ", gradients.dimension(0)));

     
    const Tensor* hessians_t;
    OP_REQUIRES_OK(context, context->input("hessians", &hessians_t));
    const auto hessians = hessians_t->matrix<float>();

     
    const Tensor* feature_t;
    OP_REQUIRES_OK(context, context->input("feature", &feature_t));
    const auto feature = feature_t->matrix<int32>();

     
    const int64_t batch_size = node_ids_t->dim_size(0);
    const int64_t logits_dims = gradients_t->dim_size(1);
    const int64_t hessians_dims = hessians_t->dim_size(1);
    const int64_t stats_dims = logits_dims + hessians_dims;
    const int64_t feature_dims = feature_t->dim_size(1);

     
     
    Tensor temp_stats_double_t;
    OP_REQUIRES_OK(
        context, context->allocate_temp(
                     DT_DOUBLE,
                     {max_splits_, feature_dims, num_buckets_ + 1, stats_dims},
                     &temp_stats_double_t));
    auto temp_stats_double = temp_stats_double_t.tensor<double, 4>();
    temp_stats_double.setZero();

    for (int i = 0; i < batch_size; ++i) {
      const int32_t node = node_ids(i);
      OP_REQUIRES(context, node >= 0,
                  errors::InvalidArgument(
                      "node_ids ", i, "th entry should be >=0, got: ", node));
      for (int feature_dim = 0; feature_dim < feature_dims; ++feature_dim) {
        const int32_t feature_value = feature(i, feature_dim);
        const int32_t bucket =
            (feature_value == -1) ? num_buckets_ : feature_value;
        for (int stat_dim = 0; stat_dim < logits_dims; ++stat_dim) {
          temp_stats_double(node, feature_dim, bucket, stat_dim) +=
              gradients(i, stat_dim);
        }
        for (int stat_dim = logits_dims; stat_dim < stats_dims; ++stat_dim) {
          temp_stats_double(node, feature_dim, bucket, stat_dim) +=
              hessians(i, stat_dim - logits_dims);
        }
      }
    }

     
    Tensor* output_stats_summary_t = nullptr;
    OP_REQUIRES_OK(context, context->allocate_output(
                                "stats_summary", temp_stats_double_t.shape(),
                                &output_stats_summary_t));
    output_stats_summary_t->tensor<float, 4>() =
        temp_stats_double.template cast<float>();
  }