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"