// 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. #pragma once #include "paddle/phi/core/kernel_registry.h" #include "paddle/phi/core/tensor_utils.h" #include "paddle/phi/kernels/funcs/cross_entropy.h" #include "paddle/phi/kernels/funcs/eigen/common.h" #include "paddle/phi/kernels/funcs/for_range.h" #include "paddle/phi/kernels/funcs/math.h" #include "paddle/phi/kernels/funcs/math_function.h" namespace phi { template void CrossEntropyOpKernel(const Context& dev_ctx, const DenseTensor& x, const DenseTensor& label, bool soft_label, int ignore_index, DenseTensor* out) { auto* labels = &label; auto* y = out; dev_ctx.template Alloc(y); int rank = x.dims().size(); auto label_dims = labels->dims(); DenseTensor x_2d = ReshapeToMatrix(x, rank - 1); DenseTensor labels_2d, y_2d; if (label_dims.size() < rank) { labels_2d.ShareDataWith(*labels); labels_2d.Resize({common::product(label_dims), 1}); y_2d.ShareDataWith(*y); y_2d.Resize({common::product(y->dims()), 1}); } else { labels_2d = ReshapeToMatrix(*labels, rank - 1); y_2d = ReshapeToMatrix(*y, rank - 1); } // TODO(large-tensor): downstream functors may still use int int64_t axis_dim = x.dims()[rank - 1]; funcs::CrossEntropyFunctor()( dev_ctx, &y_2d, &x_2d, &labels_2d, soft_label, ignore_index, axis_dim); } template class XeSoftLabelGradFunctor { public: XeSoftLabelGradFunctor(T* dx, const T* dy, // NOLINT const T* x, // NOLINT const T* label, // NOLINT size_t num_classes) : dx_(dx), dy_(dy), x_(x), label_(label), num_classes_(num_classes) {} HOSTDEVICE void operator()(size_t i) { auto row_ids = i / num_classes_; dx_[i] = -label_[i] * dy_[row_ids] / x_[i]; } private: T* dx_; const T* dy_; const T* x_; const T* label_; size_t num_classes_; }; template class XeGradFunctor { public: XeGradFunctor(T* dx, const T* dy, // NOLINT const T* x, // NOLINT const int64_t* label, // NOLINT size_t num_classes, size_t ignore_index) : dx_(dx), dy_(dy), x_(x), label_(label), num_classes_(num_classes), ignore_index_(ignore_index) {} HOSTDEVICE void operator()(size_t sample_id) { auto x_is_true_offset = sample_id * num_classes_ + label_[sample_id]; for (size_t x_offset = sample_id * num_classes_; x_offset < (sample_id + 1) * num_classes_; ++x_offset) { dx_[x_offset] = (x_offset != x_is_true_offset || label_[sample_id] == static_cast(ignore_index_)) ? static_cast(0) : -dy_[sample_id] / x_[x_offset]; } } private: T* dx_; const T* dy_; const T* x_; const int64_t* label_; size_t num_classes_; size_t ignore_index_; }; template void CrossEntropyGradientOpKernel(const Context& dev_ctx, const DenseTensor& x, const DenseTensor& label, const DenseTensor& out_grad, bool soft_label, int ignore_index, DenseTensor* x_grad) { auto* dy = &out_grad; auto* dx = x_grad; T* dx_data = dev_ctx.template Alloc(dx); // Following computation only depends on the last dimension size. So it's // unnecessary to convert tensors to 2-D views. int rank = x.dims().size(); int64_t class_num = x.dims()[rank - 1]; if (soft_label) { XeSoftLabelGradFunctor functor(dx_data, dy->data(), x.data(), label.data(), static_cast(class_num)); funcs::ForRange for_range(dev_ctx, static_cast(dx->numel())); for_range(functor); } else { XeGradFunctor functor(dx_data, dy->data(), x.data(), label.data(), static_cast(class_num), static_cast(ignore_index)); funcs::ForRange for_range(dev_ctx, static_cast(dy->numel())); for_range(functor); } } template struct HardLabelCrossEntropyForwardFunctor { HardLabelCrossEntropyForwardFunctor(const T* x, T* y, T* match_x, const int64_t* label, int64_t ignore_index, int64_t feature_size) : x_(x), y_(y), match_x_(match_x), label_(label), ignore_index_(ignore_index), feature_size_(feature_size) {} HOSTDEVICE void operator()(int64_t idx) const { auto label = label_[idx]; if (label != ignore_index_) { // don't update to PADDLE_ENFORCE_GE and PADDLE_ENFORCE_LT cause // can't use common::errors::InvalidArgument in HOSTDEVICE PADDLE_ENFORCE(label >= 0 && label < feature_size_, "Variable value (label) of " "OP(fluid.layers.cross_entropy) expected >= 0 " "and < %ld, but got %ld. Please check label value.", feature_size_, label); auto match_x = x_[idx * feature_size_ + label]; y_[idx] = -funcs::TolerableValue()(funcs::real_log(match_x)); match_x_[idx] = match_x; } else { y_[idx] = 0; match_x_[idx] = 0; // any value is ok } } const T* x_; T* y_; T* match_x_; const int64_t* label_; int64_t ignore_index_; int64_t feature_size_; }; template struct HardLabelCrossEntropyBackwardFunctor { HardLabelCrossEntropyBackwardFunctor(T* dx, const T* dy, const T* match_x, const int64_t* label, int64_t ignore_index, int64_t feature_size) : dx_(dx), dy_(dy), match_x_(match_x), label_(label), ignore_index_(ignore_index), feature_size_(feature_size) {} HOSTDEVICE void operator()(int64_t idx) const { auto row_idx = idx / feature_size_; auto col_idx = idx % feature_size_; auto label = label_[row_idx]; if (label == col_idx && label != ignore_index_) { dx_[idx] = -dy_[row_idx] / match_x_[row_idx]; } else { dx_[idx] = 0; } } T* dx_; const T* dy_; const T* match_x_; const int64_t* label_; int64_t ignore_index_; int64_t feature_size_; }; template void CrossEntropyOpKernel2(const Context& dev_ctx, const DenseTensor& x, const DenseTensor& label, int ignore_index, DenseTensor* out, DenseTensor* x_shape, DenseTensor* match_x) { auto* y = out; auto& x_dims = x.dims(); auto feature_size = x_dims[x_dims.size() - 1]; auto batch_size = common::product(x.dims()) / feature_size; auto* p_x = x.data(); auto* p_label = label.data(); auto* p_y = dev_ctx.template Alloc(y); auto* p_match_x = dev_ctx.template Alloc(match_x); funcs::ForRange for_range(dev_ctx, batch_size); for_range(HardLabelCrossEntropyForwardFunctor( p_x, p_y, p_match_x, p_label, ignore_index, feature_size)); } template void CrossEntropyGradientOpKernel2(const Context& dev_ctx, const DenseTensor& x_shape, const DenseTensor& label, const DenseTensor& match_x, const DenseTensor& out_grad, int ignore_index, DenseTensor* x_grad) { auto* dx = x_grad; auto* dy = &out_grad; auto* p_dx = dev_ctx.template Alloc(dx); auto* p_dy = dy->data(); auto* p_match_x = match_x.data(); auto* p_label = label.data(); int rank = dx->dims().size(); int64_t feature_size = dx->dims()[rank - 1]; int64_t batch_size = common::product(dx->dims()) / feature_size; funcs::ForRange for_range(dev_ctx, batch_size * feature_size); for_range(HardLabelCrossEntropyBackwardFunctor( p_dx, p_dy, p_match_x, p_label, ignore_index, feature_size)); } } // namespace phi