Files
2026-07-13 12:40:42 +08:00

286 lines
9.6 KiB
C++

// 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 <typename T, typename Context>
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<T>(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<Context, T>()(
dev_ctx, &y_2d, &x_2d, &labels_2d, soft_label, ignore_index, axis_dim);
}
template <typename T>
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 <typename T>
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<int64_t>(ignore_index_))
? static_cast<T>(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 <typename T, typename Context>
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<T>(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<T> functor(dx_data,
dy->data<T>(),
x.data<T>(),
label.data<T>(),
static_cast<size_t>(class_num));
funcs::ForRange<Context> for_range(dev_ctx,
static_cast<size_t>(dx->numel()));
for_range(functor);
} else {
XeGradFunctor<T> functor(dx_data,
dy->data<T>(),
x.data<T>(),
label.data<int64_t>(),
static_cast<size_t>(class_num),
static_cast<size_t>(ignore_index));
funcs::ForRange<Context> for_range(dev_ctx,
static_cast<size_t>(dy->numel()));
for_range(functor);
}
}
template <typename T>
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<T>()(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 <typename T>
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 <typename T, typename Context>
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<T>();
auto* p_label = label.data<int64_t>();
auto* p_y = dev_ctx.template Alloc<T>(y);
auto* p_match_x = dev_ctx.template Alloc<T>(match_x);
funcs::ForRange<Context> for_range(dev_ctx, batch_size);
for_range(HardLabelCrossEntropyForwardFunctor<T>(
p_x, p_y, p_match_x, p_label, ignore_index, feature_size));
}
template <typename T, typename Context>
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<T>(dx);
auto* p_dy = dy->data<T>();
auto* p_match_x = match_x.data<T>();
auto* p_label = label.data<int64_t>();
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<Context> for_range(dev_ctx, batch_size * feature_size);
for_range(HardLabelCrossEntropyBackwardFunctor<T>(
p_dx, p_dy, p_match_x, p_label, ignore_index, feature_size));
}
} // namespace phi