/* Copyright (c) 2016 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 #include "paddle/fluid/framework/op_registry.h" #include "paddle/fluid/operators/math/math_function.h" #include "paddle/fluid/operators/math/sequence_padding.h" #include "paddle/fluid/operators/math/sequence_scale.h" #include "paddle/fluid/platform/dynload/warpctc.h" namespace paddle { namespace operators { using Tensor = framework::Tensor; using LoDTensor = framework::LoDTensor; template 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 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 framework::ExecutionContext& ctx, const float* input, float* 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, float* cpu_loss) { // Init warp-ctc options init(ctx, blank); // Compute the required workspace size. // There is no memory allocated operations within warp-ctc. size_t workspace_bytes = 0; ctcStatus_t status = platform::dynload::get_workspace_size( cpu_label_lengths, cpu_input_lengths, static_cast(sequence_width), static_cast(num_sequences), options_, &workspace_bytes); PADDLE_ENFORCE_EQ(CTC_STATUS_SUCCESS, status, "warp-ctc [version %d] Error in get_workspace_size: ", warpctc_version_, platform::dynload::ctcGetStatusString(status)); PADDLE_ENFORCE_GT(workspace_bytes, 0UL, "Bytes of workspace got by warp-ctc function, " "get_workspace_size(), should be larger than 0."); Tensor workspace; size_t workspace_elements = workspace_bytes / sizeof(float) + 1UL; float* workspace_data = workspace.mutable_data( framework::make_ddim({static_cast(workspace_elements)}), ctx.GetPlace()); math::SetConstant()( ctx.template device_context(), &workspace, static_cast(0)); // compute loss and gradient status = platform::dynload::compute_ctc_loss( input, gradient, cpu_labels, cpu_label_lengths, cpu_input_lengths, static_cast(sequence_width), static_cast(num_sequences), cpu_loss, workspace_data, options_); PADDLE_ENFORCE_EQ(CTC_STATUS_SUCCESS, status, "warp-ctc [version %d] Error in compute_ctc_loss: ", warpctc_version_, platform::dynload::ctcGetStatusString(status)); } protected: void init(const framework::ExecutionContext& ctx, const size_t blank) { warpctc_version_ = platform::dynload::get_warpctc_version(); if (platform::is_gpu_place(ctx.GetPlace())) { #ifdef PADDLE_WITH_CUDA options_.loc = CTC_GPU; options_.stream = reinterpret_cast( ctx.device_context()) .stream(); #else PADDLE_THROW("[warpctc init] GPU is not enabled."); #endif } else { options_.loc = CTC_CPU; options_.num_threads = 1; } options_.blank_label = blank; } private: int warpctc_version_; ctcOptions options_; }; template class WarpCTCKernel : public framework::OpKernel { public: void Compute(const framework::ExecutionContext& ctx) const override { auto* logits = ctx.Input("Logits"); auto* label = ctx.Input("Label"); auto* warpctc_grad = ctx.Output("WarpCTCGrad"); auto* loss = ctx.Output("Loss"); size_t num_sequences, sequence_width, max_sequence_length; framework::Vector logits_lod; framework::Vector label_lod; if (ctx.HasInput("LogitsLength") && ctx.HasInput("LabelLength")) { num_sequences = logits->dims()[1]; sequence_width = logits->dims()[2]; max_sequence_length = logits->dims()[0]; auto* logits_length = ctx.Input("LogitsLength"); auto* labels_length = ctx.Input("LabelLength"); framework::Tensor logits_length_cpu; framework::Tensor labels_length_cpu; framework::TensorCopy(*logits_length, platform::CPUPlace(), &logits_length_cpu); framework::TensorCopy(*labels_length, platform::CPUPlace(), &labels_length_cpu); logits_lod.push_back(0); label_lod.push_back(0); for (auto i = 0; i < num_sequences; i++) { logits_lod.push_back(logits_lod[i] + logits_length_cpu.data()[i]); label_lod.push_back(label_lod[i] + labels_length_cpu.data()[i]); } } else { logits_lod = framework::ToAbsOffset(logits->lod())[0]; auto logits_dims = logits->dims(); PADDLE_ENFORCE_EQ( logits_dims[0], static_cast(logits_lod.back()), "The first dimension of Input(Logits) should be equal to " "the sum of all sequences' lengths."); label_lod = framework::ToAbsOffset(label->lod())[0]; auto label_dims = label->dims(); PADDLE_ENFORCE_EQ( label_dims[0], label->numel(), "The width of each timestep in Input(Label) should be 1."); num_sequences = logits_lod.size() - 1; PADDLE_ENFORCE_EQ(num_sequences, label_lod.size() - 1, "The number of sequences of Input(Logits) should be " "equal to that of Input(Label)."); sequence_width = logits->numel() / logits_dims[0]; max_sequence_length = math::MaximumSequenceLength(logits_lod); } auto loss_dims = framework::make_ddim({static_cast(num_sequences), 1}); // warpctc needs sequences data stored in transposed padding format LoDTensor warpctc_logits; auto warpctc_logits_dims = framework::make_ddim({static_cast(max_sequence_length), static_cast(num_sequences), static_cast(sequence_width)}); warpctc_logits.mutable_data(warpctc_logits_dims, ctx.GetPlace()); if (ctx.HasInput("LogitsLength")) { TensorCopySync(*logits, ctx.GetPlace(), &warpctc_logits); } else { LoDTensor cpu_pad_value; T* pad_value_data = cpu_pad_value.mutable_data({1}, platform::CPUPlace()); *pad_value_data = static_cast(0); LoDTensor pad_value; if (platform::is_cpu_place(ctx.GetPlace())) { pad_value = cpu_pad_value; } else { TensorCopySync(cpu_pad_value, ctx.GetPlace(), &pad_value); } math::PaddingLoDTensorFunctor()( ctx.template device_context(), *logits, &warpctc_logits, pad_value, -1, 0, false /* norm_by_times */, math::kLengthBatchWidth); } const T* warpctc_logits_data = warpctc_logits.data(); std::vector warpctc_label_lengths(num_sequences); std::vector warpctc_logits_lengths(num_sequences); for (size_t i = 0; i < num_sequences; ++i) { warpctc_label_lengths[i] = label_lod[i + 1] - label_lod[i]; warpctc_logits_lengths[i] = logits_lod[i + 1] - logits_lod[i]; } // warpctc computes loss and gradient in one call, gradient data also stored // in batch format T* warpctc_grad_data = warpctc_grad->mutable_data(warpctc_logits.dims(), ctx.GetPlace()); math::SetConstant()( ctx.template device_context(), warpctc_grad, static_cast(0)); // warpctc accesses labels in CPU memory Tensor warpctc_label; TensorCopySync(*label, platform::CPUPlace(), &warpctc_label); const int* warpctc_label_data = warpctc_label.data(); // warpctc stores loss in CPU memory Tensor warpctc_loss; T* warpctc_loss_data = warpctc_loss.mutable_data(loss_dims, platform::CPUPlace()); const size_t blank = static_cast(ctx.Attr("blank")); WarpCTCFunctor()( ctx, warpctc_logits_data, warpctc_grad_data, warpctc_label_data, warpctc_label_lengths.data(), warpctc_logits_lengths.data(), sequence_width, num_sequences, blank, warpctc_loss_data); // Copy the loss back TensorCopy(warpctc_loss, ctx.GetPlace(), ctx.device_context(), loss); } }; template class WarpCTCGradKernel : public framework::OpKernel { public: void Compute(const framework::ExecutionContext& ctx) const override { auto* warpctc_grad = ctx.Input("WarpCTCGrad"); auto* logits_grad = ctx.Output(framework::GradVarName("Logits")); const Tensor* loss_grad = ctx.Input(framework::GradVarName("Loss")); logits_grad->mutable_data(ctx.GetPlace()); bool norm_by_times = ctx.Attr("norm_by_times"); if (ctx.HasInput("LogitsLength")) { size_t max_seq_length = warpctc_grad->dims()[0]; size_t num_sequences = warpctc_grad->dims()[1]; size_t seq_width = warpctc_grad->dims()[2]; LoDTensor logits_grad_with_lod; auto logits_grad_dims = framework::make_ddim({static_cast(max_seq_length), static_cast(num_sequences), static_cast(seq_width)}); T* logits_grad_cpu_data = logits_grad_with_lod.mutable_data( logits_grad_dims, platform::CPUPlace()); TensorCopySync(*warpctc_grad, platform::CPUPlace(), &logits_grad_with_lod); Tensor loss_grad_cpu; loss_grad_cpu.mutable_data(loss_grad->dims(), platform::CPUPlace()); TensorCopySync(*loss_grad, platform::CPUPlace(), &loss_grad_cpu); LoDTensor scaled_logits; T* scaled_logits_data = scaled_logits.mutable_data(logits_grad_dims, platform::CPUPlace()); const T* loss_grad_data = loss_grad_cpu.data(); for (size_t i = 0; i < max_seq_length; ++i) { for (size_t j = 0; j < num_sequences; ++j) { for (size_t k = 0; k < seq_width; ++k) { size_t idx = i * (num_sequences * seq_width) + j * seq_width + k; scaled_logits_data[idx] = logits_grad_cpu_data[idx] * loss_grad_data[j]; } } } TensorCopySync(scaled_logits, ctx.GetPlace(), logits_grad); } else { math::UnpaddingLoDTensorFunctor()( ctx.template device_context(), *warpctc_grad, logits_grad, -1, 0, norm_by_times, math::kLengthBatchWidth); const T* loss_grad_data = loss_grad->data(); math::ScaleLoDTensorFunctor()( ctx.template device_context(), loss_grad_data, logits_grad); } } }; } // namespace operators } // namespace paddle