File indexing completed on 2024-11-24 04:16:55

0001 /*
0002   SPDX-FileCopyrightText: 2023-2024 Laurent Montel <montel.org>
0003 
0004   SPDX-License-Identifier: GPL-2.0-or-later
0005 
0006   Based on translatelocally code
0007 */
0008 
0009 #include "bergamotmarianinterface.h"
0010 #include "libbergamot_debug.h"
0011 #include <KLocalizedString>
0012 #include <chrono>
0013 #include <future>
0014 #include <memory>
0015 #include <mutex>
0016 #include <slimt/Response.hh>
0017 #include <thread>
0018 namespace
0019 {
0020 #if 0
0021 std::shared_ptr<marian::Options> makeOptions(const std::string &path_to_model_dir, const BergamotEngineUtils::SettingsInfo &settings)
0022 {
0023     std::shared_ptr<marian::Options> options(slimt::parseOptionsFromFilePath(path_to_model_dir + "/config.intgemm8bitalpha.yml"));
0024     options->set("cpu-threads", settings.numberOfThread, "workspace", settings.memoryByThread, "mini-batch-words", 1000, "alignment", "soft", "quiet", true);
0025     return options;
0026 }
0027 #endif
0028 
0029 int countWords(std::string input)
0030 {
0031     const char *str = input.c_str();
0032 
0033     bool inSpaces = true;
0034     int numWords = 0;
0035 
0036     while (*str != '\0') {
0037         if (std::isspace(static_cast<unsigned char>(*str))) {
0038             inSpaces = true;
0039         } else if (inSpaces) {
0040             numWords++;
0041             inSpaces = false;
0042         }
0043         ++str;
0044     }
0045     return numWords;
0046 }
0047 
0048 } // Anonymous namespace
0049 
0050 struct TranslationInput {
0051     std::string text;
0052 #if 0
0053     slimt::ResponseOptions options;
0054 #endif
0055 };
0056 
0057 struct ModelDescription {
0058     std::string config_file;
0059     BergamotEngineUtils::SettingsInfo settings;
0060 };
0061 
0062 constexpr const size_t kTranslationCacheSize = 1 << 16;
0063 
0064 BergamotMarianInterface::BergamotMarianInterface(QObject *parent)
0065     : QObject{parent}
0066     , mPendingInput(nullptr)
0067     , mPendingModel(nullptr)
0068 {
0069 #if 0
0070     // This worker is the only thread that can interact with Marian. Right now
0071     // it basically uses slimt::Service's non-blocking interface
0072     // in a blocking way to have an easy way to control how what the next
0073     // task will be, and to not start queueing up already irrelevant
0074     // translation operations.
0075     // This worker basically processes a command queue, except that there are
0076     // only two possible commands: load model & translate input. And there are
0077     // no actual queues because we always want the last command: we don't care
0078     // about previously pending models or translations. The semaphore
0079     // indicates whether there are 0, 1, or 2 commands pending. If a command
0080     // is pending but both "queues" are empty, we'll treat that as a shutdown
0081     // request.
0082     mWorke = std::thread([&]() {
0083         std::unique_ptr<slimt::AsyncService> service;
0084         std::shared_ptr<slimt::TranslationModel> model;
0085 
0086         std::mutex internal_mutex;
0087 
0088         while (true) {
0089             std::unique_ptr<ModelDescription> modelChange;
0090             std::unique_ptr<TranslationInput> input;
0091 
0092             {
0093                 // Wait for work
0094                 std::unique_lock<std::mutex> lock(mMutex);
0095                 mConditionVariable.wait(lock, [&] {
0096                     return mPendingModel || mPendingInput || mPendingShutdown;
0097                 });
0098 
0099                 // First check whether the command is loading a new model
0100                 if (mPendingModel)
0101                     modelChange = std::move(mPendingModel);
0102 
0103                 // Second check whether command is translating something.
0104                 // Note: else if because we only process one command per
0105                 // iteration otherwise commandIssued_ would go out of sync.
0106                 else if (mPendingInput)
0107                     input = std::move(mPendingInput);
0108 
0109                 // Command without any pending change -> poison.
0110                 else
0111                     break;
0112             }
0113 
0114             Q_EMIT pendingChanged(true);
0115 
0116             try {
0117                 if (modelChange) {
0118                     // Reconstruct the service because cpu_threads might have changed.
0119                     // @TODO: don't recreate Service if cpu_threads didn't change?
0120                     slimt::AsyncService::Config serviceConfig;
0121                     serviceConfig.numWorkers = modelChange->settings.numberOfThread;
0122                     serviceConfig.cacheSize = modelChange->settings.useLocalCache ? kTranslationCacheSize : 0;
0123 
0124                     // Free up old service first (see https://github.com/browsermt/bergamot-translator/issues/290)
0125                     // Calling clear to remove any pending translations so we
0126                     // do not have to wait for those when AsyncService is destroyed.
0127                     service.reset();
0128 
0129                     service = std::make_unique<slimt::AsyncService>(serviceConfig);
0130 
0131                     // Initialise a new model. Old model will be released if
0132                     // service is done with it, which it is since all translation
0133                     // requests are effectively blocking in this thread.
0134                     auto modelConfig = makeOptions(modelChange->config_file, modelChange->settings);
0135                     model = std::make_shared<slimt::TranslationModel>(modelConfig, modelChange->settings.numberOfThread);
0136                 } else if (input) {
0137                     if (model) {
0138                         std::future<int> wordCount = std::async(
0139                             countWords,
0140                             input->text); // @TODO we're doing an "unnecessary" string copy here (necessary because we std::move input into service->translate)
0141 
0142                         Translation translation;
0143 
0144                         // Measure the time it takes to queue and respond to the
0145                         // translation request
0146                         service->translate(
0147                             model,
0148                             std::move(input->text),
0149                             [&](auto &&val) {
0150                                 // Calculate translation speed in terms of words per second
0151                                 std::unique_lock<std::mutex> lock(internal_mutex);
0152                                 translation = Translation(std::move(val));
0153                                 mConditionVariable.notify_one();
0154                             },
0155                             input->options);
0156 
0157                         // Wait for either translate lambda to call back, or a reason to cancel
0158                         std::unique_lock<std::mutex> lock(internal_mutex);
0159                         mConditionVariable.wait(lock, [&] {
0160                             return translation || mPendingShutdown || mPendingModel;
0161                         });
0162 
0163                         if (translation)
0164                             Q_EMIT translationReady(translation);
0165                         else
0166                             service->clear(); // translation was interrupted. Clear pending batches
0167                                               // now to free any references to things that will go
0168                                               // out of scope.
0169                     } else {
0170                         // TODO: What? Raise error? Set model_ to ""?
0171                     }
0172                 }
0173             } catch (const std::runtime_error &e) {
0174                 Q_EMIT errorText(QString::fromStdString(e.what()));
0175             }
0176 
0177             Q_EMIT pendingChanged(false);
0178         }
0179     });
0180 #endif
0181 }
0182 
0183 BergamotMarianInterface::~BergamotMarianInterface()
0184 {
0185 #if 0
0186     // Remove all pending changes and unlock worker (which will then break.)
0187     {
0188         std::unique_lock<std::mutex> lock(mMutex);
0189 
0190         mPendingShutdown = true;
0191         mPendingModel.reset();
0192         mPendingInput.reset();
0193 
0194         mConditionVariable.notify_one();
0195     }
0196 
0197     // Wait for worker to join as it depends on resources we still own.
0198     mWorke.join();
0199 #endif
0200 }
0201 
0202 void BergamotMarianInterface::translate(const QString &str)
0203 {
0204 #if 0
0205     // If we don't have a model yet (loaded, or queued to be loaded, doesn't matter)
0206     // then don't bother trying to translate something.
0207     if (mModelString.isEmpty()) {
0208         qCWarning(TRANSLATOR_LIBBERGAMOT_LOG) << " mModelString is not defined!!!";
0209         Q_EMIT errorText(i18n("Language model is not defined."));
0210         return;
0211     }
0212 
0213     std::unique_lock<std::mutex> lock(mMutex);
0214     std::unique_ptr<TranslationInput> input(new TranslationInput{str.toStdString(), slimt::ResponseOptions{}});
0215     input->options.alignment = true;
0216     input->options.HTML = false;
0217 
0218     std::swap(mPendingInput, input);
0219 
0220     mConditionVariable.notify_one();
0221 #endif
0222 }
0223 
0224 QString BergamotMarianInterface::model() const
0225 {
0226     return mModelString;
0227 }
0228 
0229 void BergamotMarianInterface::setModel(const QString &pathModelDir, const BergamotEngineUtils::SettingsInfo &settings)
0230 {
0231     mModelString = pathModelDir;
0232 
0233     // Empty model string means just "unload" the model. We don't do that (yet),
0234     // instead this just causes translate(QString) to no longer work.
0235     if (mModelString.isEmpty())
0236         return;
0237 
0238 #if 0
0239     // move my shared_ptr from stack to heap
0240     std::unique_lock<std::mutex> lock(mMutex);
0241     std::unique_ptr<ModelDescription> model(new ModelDescription{mModelString.toStdString(), settings});
0242     std::swap(mPendingModel, model);
0243 
0244     // notify worker if there wasn't already a pending model
0245     mConditionVariable.notify_one();
0246 #endif
0247 }
0248 
0249 #include "moc_bergamotmarianinterface.cpp"