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

460 lines
18 KiB
C++

// Copyright (c) 2022 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 <vector>
#include "paddle/common/enforce.h"
#include "paddle/phi/backends/dynload/warpctc.h"
#include "paddle/phi/core/dense_tensor.h"
#include "paddle/phi/core/lod_utils.h"
#include "paddle/phi/core/tensor_utils.h"
#include "paddle/phi/kernels/empty_kernel.h"
#include "paddle/phi/kernels/funcs/math_function.h"
#include "paddle/phi/kernels/funcs/sequence_padding.h"
#include "paddle/phi/kernels/funcs/sequence_scale.h"
#include "paddle/utils/optional.h"
namespace phi {
template <typename Context, typename T>
class ComputeCtcLossFunctor {
public:
ctcStatus_t operator()(const T* const activations,
T* gradients,
const int* const flat_labels,
const int* const label_lengths,
const int* const input_lengths,
int alphabet_size,
int minibatch,
T* costs,
void* workspace,
ctcOptions options) {
return CTC_STATUS_EXECUTION_FAILED;
}
};
template <typename Context>
class ComputeCtcLossFunctor<Context, float> {
public:
ctcStatus_t operator()(const float* const activations,
float* gradients,
const int* const flat_labels,
const int* const label_lengths,
const int* const input_lengths,
int alphabet_size,
int minibatch,
float* costs,
void* workspace,
ctcOptions options) {
return dynload::compute_ctc_loss(activations,
gradients,
flat_labels,
label_lengths,
input_lengths,
static_cast<int>(alphabet_size),
static_cast<int>(minibatch),
costs,
workspace,
options);
}
};
template <typename Context>
class ComputeCtcLossFunctor<Context, double> {
public:
ctcStatus_t operator()(const double* const activations,
double* gradients,
const int* const flat_labels,
const int* const label_lengths,
const int* const input_lengths,
int alphabet_size,
int minibatch,
double* costs,
void* workspace,
ctcOptions options) {
return dynload::compute_ctc_loss_double(activations,
gradients,
flat_labels,
label_lengths,
input_lengths,
static_cast<int>(alphabet_size),
static_cast<int>(minibatch),
costs,
workspace,
options);
}
};
template <typename Context, typename T>
class WarpCTCFunctor {
public:
/*
* \brief Compute the connectionist temporal classification loss,
* and optionally compute the gradient with respect to the inputs.
*
* If gradient is nullptr, it only computes the ctc loss,
* or computes both ctc loss and gradient.
*
* \param dev_ctx execution context of this functor
* \param input batch matrix of input probabilities, in
* max_sequence_length x num_sequences x
* sequence_width, (row-major) format
* \param gradient batch matrix of gradient, with the same shape as
* input.
* \param cpu_labels labels always in CPU memory.
* \param cpu_label_lengths length of all labels in CPU memory.
* \param cpu_input_lengths length of all sequences in CPU memory.
* \param sequence_width number of possible output symbols.
* \param num_sequences number of sequence.
* \param blank blank label used in ctc loss function.
* \param cpu_losss cost of each sequence in CPU memory.
*/
void operator()(const Context& dev_ctx,
const T* input,
T* gradient,
const int* cpu_labels,
const int* cpu_label_lengths,
const int* cpu_input_lengths,
const size_t sequence_width,
const size_t num_sequences,
const size_t blank,
T* cpu_loss) {
// Init warp-ctc options
init(dev_ctx, blank);
PADDLE_ENFORCE_LE_INT_MAX(sequence_width, "warpctc sequence width");
PADDLE_ENFORCE_LE_INT_MAX(num_sequences, "warpctc num sequences");
const int sequence_width_int = static_cast<int>(sequence_width);
const int num_sequences_int = static_cast<int>(num_sequences);
// Compute the required workspace size.
// There is no memory allocated operations within warp-ctc.
size_t workspace_bytes = 0;
ctcStatus_t status = CTC_STATUS_UNKNOWN_ERROR;
if (sizeof(T) == 4) {
status = dynload::get_workspace_size(cpu_label_lengths,
cpu_input_lengths,
sequence_width_int,
num_sequences_int,
options_,
&workspace_bytes);
} else {
status = dynload::get_workspace_size_double(cpu_label_lengths,
cpu_input_lengths,
sequence_width_int,
num_sequences_int,
options_,
&workspace_bytes);
}
PADDLE_ENFORCE_EQ(
CTC_STATUS_SUCCESS,
status,
errors::PreconditionNotMet(
"warp-ctc [version %d] Error in get_workspace_size: %s",
warpctc_version_,
dynload::ctcGetStatusString(status)));
PADDLE_ENFORCE_GT(
workspace_bytes,
0UL,
errors::InvalidArgument(
"Bytes of workspace got by warp-ctc function, "
"get_workspace_size() should be larger than 0, but received %d",
workspace_bytes));
size_t workspace_elements = workspace_bytes / sizeof(T) + 1UL;
DenseTensor workspace =
Empty<T, Context>(dev_ctx, {static_cast<int64_t>(workspace_elements)});
T* workspace_data = workspace.data<T>();
funcs::SetConstant<Context, T>()(dev_ctx, &workspace, static_cast<T>(0));
// compute loss and gradient
status = ComputeCtcLossFunctor<Context, T>()(input,
gradient,
cpu_labels,
cpu_label_lengths,
cpu_input_lengths,
sequence_width_int,
num_sequences_int,
cpu_loss,
workspace_data,
options_);
PADDLE_ENFORCE_EQ(
CTC_STATUS_SUCCESS,
status,
errors::PreconditionNotMet(
"warp-ctc [version %d] Error in get_workspace_size: %s",
warpctc_version_,
dynload::ctcGetStatusString(status)));
}
protected:
void init(const Context& dev_ctx, const size_t blank) {
warpctc_version_ = dynload::get_warpctc_version();
PADDLE_ENFORCE_LE_INT_MAX(blank, "warpctc blank_label");
if (dev_ctx.GetPlace().GetType() != AllocationType::CPU) {
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
options_.loc = CTC_GPU;
options_.stream = reinterpret_cast<const GPUContext&>(dev_ctx).stream();
#else
PADDLE_THROW(
errors::PreconditionNotMet("[warpctc init] GPU is not enabled."));
#endif
} else {
options_.loc = CTC_CPU;
options_.num_threads = 1;
}
options_.blank_label = static_cast<int>(blank);
}
private:
int warpctc_version_;
ctcOptions options_;
};
template <typename T, typename Context>
void WarpctcKernel(const Context& dev_ctx,
const DenseTensor& logits,
const DenseTensor& label,
const optional<DenseTensor>& logits_length,
const optional<DenseTensor>& labels_length,
int blank,
bool norm_by_times UNUSED,
DenseTensor* loss,
DenseTensor* warpctcgrad) {
size_t num_sequences, sequence_width, max_sequence_length;
Vector<size_t> logits_lod;
Vector<size_t> label_lod;
if (logits_length.is_initialized() && labels_length.is_initialized()) {
num_sequences = logits.dims()[1];
sequence_width = logits.dims()[2];
max_sequence_length = logits.dims()[0];
PADDLE_ENFORCE_GT(max_sequence_length,
0,
common::errors::InvalidArgument(
"The first dimension of Input(Logits) should be "
"greater than zero "
"but received %d. ",
max_sequence_length));
PADDLE_ENFORCE_GT(num_sequences,
0,
common::errors::InvalidArgument(
"The second dimension of Input(Logits) should be "
"greater than zero "
"but received %d. ",
num_sequences));
PADDLE_ENFORCE_GT(sequence_width,
0,
common::errors::InvalidArgument(
"The third dimension of Input(Logits) should be "
"greater than zero "
"but received %d. ",
sequence_width));
PADDLE_ENFORCE_LT(
num_sequences * sequence_width * max_sequence_length,
std::numeric_limits<int>::max(),
errors::InvalidArgument(
"The total number of elements in Input(Logits) should be less than "
"%d, "
"but received %d",
std::numeric_limits<int>::max(),
num_sequences * sequence_width * max_sequence_length));
DenseTensor logits_length_cpu;
DenseTensor labels_length_cpu;
Copy(dev_ctx, *logits_length, CPUPlace(), false, &logits_length_cpu);
Copy(dev_ctx, *labels_length, CPUPlace(), false, &labels_length_cpu);
logits_lod.push_back(0);
label_lod.push_back(0);
for (size_t i = 0; i < num_sequences; i++) {
logits_lod.push_back(logits_lod[i] +
logits_length_cpu.data<int64_t>()[i]);
label_lod.push_back(label_lod[i] + labels_length_cpu.data<int64_t>()[i]);
}
} else {
PADDLE_ENFORCE_GT(
logits.NumLevels(),
0UL,
common::errors::InvalidArgument("Input(Logits) Tensor of WarpCTC "
"does not contain LoD information."));
PADDLE_ENFORCE_GT(
label.NumLevels(),
0UL,
common::errors::InvalidArgument("Input(Label) Tensor of WarpCTC "
"does not contain LoD information."));
logits_lod = ToAbsOffset(logits.lod())[0];
auto logits_dims = logits.dims();
PADDLE_ENFORCE_GT(logits_dims[0],
0,
common::errors::InvalidArgument(
"The first dimension of Input(Logits) should be "
"greater than zero "
"but received %d. ",
logits_dims[0]));
PADDLE_ENFORCE_EQ(
logits_dims[0],
static_cast<int64_t>(logits_lod.back()),
common::errors::InvalidArgument(
"The first dimension of Input(Logits) should be equal to "
"the sum of all sequences' lengths = %d., but received %d. ",
static_cast<int64_t>(logits_lod.back()),
logits_dims[0]));
label_lod = ToAbsOffset(label.lod())[0];
auto label_dims = label.dims();
PADDLE_ENFORCE_EQ(label_dims[1],
1,
common::errors::InvalidArgument(
"The last dimension of Input(Label) should be 1, "
"but received %d",
label_dims[1]));
num_sequences = logits_lod.size() - 1;
PADDLE_ENFORCE_EQ(num_sequences,
label_lod.size() - 1,
common::errors::InvalidArgument(
"The number of sequences of Input(Logits) should be "
"equal to that of Input(Label) = %d, but received %d",
label_lod.size() - 1,
num_sequences));
sequence_width = logits.numel() / logits_dims[0];
max_sequence_length = funcs::MaximumSequenceLength(logits_lod);
}
auto loss_dims = make_ddim({static_cast<int64_t>(num_sequences), 1});
// warpctc needs sequences data stored in transposed padding format
DenseTensor warpctc_logits_tmp =
Empty<T, Context>(dev_ctx,
{static_cast<int64_t>(max_sequence_length),
static_cast<int64_t>(num_sequences),
static_cast<int64_t>(sequence_width)});
DenseTensor warpctc_logits(warpctc_logits_tmp);
if (logits_length.is_initialized()) {
Copy(dev_ctx, logits, dev_ctx.GetPlace(), true, &warpctc_logits);
} else {
DenseTensor cpu_pad_value;
cpu_pad_value.Resize({1});
T* pad_value_data = dev_ctx.template HostAlloc<T>(&cpu_pad_value);
*pad_value_data = static_cast<T>(0);
DenseTensor pad_value;
if (dev_ctx.GetPlace() == CPUPlace()) {
pad_value = cpu_pad_value;
} else {
Copy(dev_ctx, cpu_pad_value, dev_ctx.GetPlace(), true, &pad_value);
}
funcs::PaddingDenseTensorFunctor<Context, T>()(dev_ctx,
logits,
&warpctc_logits,
pad_value,
-1,
0,
false /* norm_by_times */,
funcs::kLengthBatchWidth);
}
const T* warpctc_logits_data = warpctc_logits.data<T>();
PADDLE_ENFORCE_LE_INT_MAX(label.dims()[1], "warpctc label pad length");
const int label_pad_length = static_cast<int>(label.dims()[1]);
std::vector<int> warpctc_label_lengths(num_sequences);
std::vector<int> warpctc_logits_lengths(num_sequences);
for (size_t i = 0; i < num_sequences; ++i) {
const size_t label_length = label_lod[i + 1] - label_lod[i];
const size_t logits_length = logits_lod[i + 1] - logits_lod[i];
PADDLE_ENFORCE_LE_INT_MAX(label_length, "warpctc label length");
PADDLE_ENFORCE_LE_INT_MAX(logits_length, "warpctc logits length");
warpctc_label_lengths[i] = static_cast<int>(label_length);
warpctc_logits_lengths[i] = static_cast<int>(logits_length);
}
// warpctc computes loss and gradient in one call, gradient data also stored
// in batch format
warpctcgrad->Resize(warpctc_logits.dims());
T* warpctcgrad_data = dev_ctx.template Alloc<T>(warpctcgrad);
funcs::SetConstant<Context, T>()(dev_ctx, warpctcgrad, static_cast<T>(0));
// warpctc accesses labels in CPU memory
DenseTensor warpctc_label;
if (logits_length.is_initialized()) {
warpctc_label.Resize(
{static_cast<int64_t>(funcs::TotalSequenceLength(label_lod)), 1});
dev_ctx.template HostAlloc<int>(&warpctc_label);
std::vector<Vector<size_t>> lod;
lod.push_back(label_lod);
warpctc_label.set_lod(lod);
if (dev_ctx.GetPlace() == CPUPlace()) {
funcs::UnpaddingDenseTensorFunctor<Context, int>()(
dev_ctx,
label,
&warpctc_label,
label_pad_length /*pad_seq_len*/,
0 /*lod_level*/,
false /*norm_by_times*/,
funcs::kBatchLengthWidth);
} else {
DenseTensor gpu_label;
gpu_label.Resize(
{static_cast<int64_t>(funcs::TotalSequenceLength(label_lod)), 1});
dev_ctx.template Alloc<int>(&gpu_label);
gpu_label.set_lod(lod);
funcs::UnpaddingDenseTensorFunctor<Context, int>()(
dev_ctx,
label,
&gpu_label,
label_pad_length /*pad_seq_len*/,
0 /*lod_level*/,
false /*norm_by_times*/,
funcs::kBatchLengthWidth);
Copy(dev_ctx, gpu_label, CPUPlace(), true, &warpctc_label);
}
} else {
Copy(dev_ctx, label, CPUPlace(), true, &warpctc_label);
}
const int* warpctc_label_data = warpctc_label.data<int>();
// warpctc stores loss in CPU memory
DenseTensor warpctc_loss;
warpctc_loss.Resize(loss_dims);
T* warpctc_loss_data = dev_ctx.template HostAlloc<T>(&warpctc_loss);
WarpCTCFunctor<Context, T>()(dev_ctx,
warpctc_logits_data,
warpctcgrad_data,
warpctc_label_data,
warpctc_label_lengths.data(),
warpctc_logits_lengths.data(),
sequence_width,
num_sequences,
blank,
warpctc_loss_data);
// Copy the loss back
Copy(dev_ctx, warpctc_loss, dev_ctx.GetPlace(), false, loss);
}
} // namespace phi