// Copyright (c) 2024 PaddlePaddle Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include "paddle/phi/core/kernel_registry.h" #include "paddle/phi/kernels/funcs/eigen/common.h" #include "paddle/phi/kernels/funcs/math/sampler.h" #include "paddle/phi/kernels/funcs/selected_rows_functor.h" #include "paddle/utils/optional.h" namespace phi { namespace sr { using Sampler = phi::math::Sampler; template void NCEGradKernel(const Context &dev_ctx, const DenseTensor &input_in, const DenseTensor &label_in, const optional &bias_in, const DenseTensor &weight_in, const DenseTensor &sample_logits_in, const DenseTensor &sample_labels_in, const optional &sample_weight_in, const optional &custom_dist_probs, const optional &custom_dist_alias, const optional &custom_dist_alias_probs, const DenseTensor &cost_grad, int num_total_classes, const std::vector &custom_neg_classes, int num_neg_samples, int sampler_in, int seed, bool is_sparse, bool remote_prefetch, bool is_test, DenseTensor *input_grad, DenseTensor *bias_grad, SelectedRows *weight_grad) { auto d_out = &cost_grad; const T *d_out_data = d_out->data(); auto label = &label_in; auto sample_out = &sample_logits_in; const T *sample_out_data = sample_out->data(); auto sample_labels = &sample_labels_in; const int64_t *sample_labels_data = sample_labels->data(); auto sample_weight = sample_weight_in.get_ptr(); const T *sample_weight_data = nullptr; if (sample_weight != nullptr) { sample_weight_data = sample_weight->data(); } int num_true_class = 1; if (label != nullptr) { num_true_class = label->dims()[1]; } int sampler_type = sampler_in; Sampler *sampler; switch (sampler_type) { case 0: { sampler = new phi::math::UniformSampler(num_total_classes - 1, seed); break; } case 1: { sampler = new phi::math::LogUniformSampler(num_total_classes - 1, seed); break; } case 2: { auto dist_probs = custom_dist_probs.get_ptr(); auto dist_alias = custom_dist_alias.get_ptr(); auto dist_alias_probs = custom_dist_alias_probs.get_ptr(); PADDLE_ENFORCE_EQ( dist_probs->numel(), num_total_classes, common::errors::InvalidArgument( "ShapeError: The number of elements in Input(CustomDistProbs) " "should be equal to the number of total classes. But Received: " "Input(CustomDistProbs).numel() = %d, Attr(num_total_classes) " "= %d.", dist_probs->numel(), num_total_classes)); PADDLE_ENFORCE_EQ( dist_alias->numel(), num_total_classes, common::errors::InvalidArgument( "ShapeError: The number of elements in Input(CustomDistAlias) " "should be equal to the number of total classes. But Received: " "Input(CustomDistAlias).numel() = %d, Attr(num_total_classes) " "= %d.", dist_alias->numel(), num_total_classes)); PADDLE_ENFORCE_EQ( dist_alias_probs->numel(), num_total_classes, common::errors::InvalidArgument( "ShapeError: The number of elements in " "Input(CustomDistAliasProbs) " "should be equal to the number of total classes. But Received: " "Input(CustomDistAliasProbs).numel() = %d, " "Attr(num_total_classes) = %d.", dist_alias_probs->numel(), num_total_classes)); const float *probs_data = dist_probs->data(); const int *alias_data = dist_alias->data(); const float *alias_probs_data = dist_alias_probs->data(); sampler = new phi::math::CustomSampler(num_total_classes - 1, probs_data, alias_data, alias_probs_data, seed); break; } default: { PADDLE_THROW(common::errors::InvalidArgument( "Unsupported SamplerType. SamplerType should be 0: Uniform, " "1: LogUniform or 2: CustomDist. Received SamplerType: %d", sampler_type)); } } // T b = 1. / num_total_classes * num_neg_samples; DenseTensor sample_grad; // tmp tensor sample_grad.Resize(sample_labels->dims()); T *sample_grad_data = dev_ctx.template Alloc(&sample_grad); // backward cost for (int64_t i = 0; i < sample_labels->numel(); ++i) { int64_t label_idx = i % sample_labels->dims()[1]; int64_t sample_idx = i / sample_labels->dims()[1]; float b = sampler->Probability(sample_labels_data[i]) * num_neg_samples; T o = sample_out_data[i]; T w = sample_weight == nullptr ? 1 : sample_weight_data[sample_idx]; sample_grad_data[i] = label_idx < num_true_class ? w * (b / (o + b)) * (o - 1) : w * (o * (1 - o) / (o + b)); sample_grad_data[i] *= d_out_data[sample_idx]; } // get d_bias auto d_bias = bias_grad; if (d_bias != nullptr) { T *d_bias_data = dev_ctx.template Alloc(d_bias); std::fill(d_bias_data, d_bias_data + d_bias->numel(), 0.0); for (int64_t i = 0; i < sample_labels->numel(); ++i) { d_bias_data[sample_labels_data[i]] += sample_grad_data[i]; } } if (!is_sparse) { PADDLE_THROW( common::errors::InvalidArgument("The parameter weight_grad of a NCE_OP " "must be DenseTensor")); } else { std::vector labels; for (int64_t i = 0; i < sample_labels->numel(); ++i) { labels.push_back(sample_labels_data[i]); } std::set st(labels.begin(), labels.end()); labels.assign(st.begin(), st.end()); DDim table_dim = weight_in.dims(); auto d_w = weight_grad; d_w->set_rows(labels); d_w->set_height(table_dim[0]); auto *d_table_value = d_w->mutable_value(); d_table_value->Resize({static_cast(labels.size()), table_dim[1]}); auto d_w_data = dev_ctx.template Alloc(d_table_value); std::fill(d_w_data, d_w_data + d_table_value->numel(), 0.0); auto d_w_matrix = EigenMatrix::From(*d_table_value); auto x_matrix = EigenMatrix::From(input_in); for (int64_t i = 0; i < sample_labels->numel(); ++i) { d_w_matrix.chip(d_w->Index(sample_labels_data[i]), 0) += x_matrix.chip(static_cast(i / sample_labels->dims()[1]), 0) * sample_grad_data[i]; } } // get d_x auto d_x = input_grad; if (d_x != nullptr) { auto *d_x_data = dev_ctx.template Alloc(d_x); std::fill(d_x_data, d_x_data + d_x->numel(), 0.0); auto d_x_matrix = EigenMatrix::From(*d_x); auto w_matrix = EigenMatrix::From(weight_in); for (int64_t i = 0; i < sample_labels->numel(); ++i) { d_x_matrix.chip(static_cast(i / sample_labels->dims()[1]), 0) += w_matrix.chip(sample_labels_data[i], 0) * sample_grad_data[i]; } } delete sampler; } } // namespace sr } // namespace phi PD_REGISTER_KERNEL( nce_sr_grad, CPU, ALL_LAYOUT, phi::sr::NCEGradKernel, float, double) {}