KTextAddons

bergamotmarianinterface.cpp
1/*
2 SPDX-FileCopyrightText: 2023-2024 Laurent Montel <montel.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5
6 Based on translatelocally code
7*/
8
9#include "bergamotmarianinterface.h"
10#include "libbergamot_debug.h"
11#include <KLocalizedString>
12#include <chrono>
13#include <future>
14#include <memory>
15#include <mutex>
16#include <slimt/Response.hh>
17#include <thread>
18namespace
19{
20#if 0
21std::shared_ptr<marian::Options> makeOptions(const std::string &path_to_model_dir, const BergamotEngineUtils::SettingsInfo &settings)
22{
23 std::shared_ptr<marian::Options> options(slimt::parseOptionsFromFilePath(path_to_model_dir + "/config.intgemm8bitalpha.yml"));
24 options->set("cpu-threads", settings.numberOfThread, "workspace", settings.memoryByThread, "mini-batch-words", 1000, "alignment", "soft", "quiet", true);
25 return options;
26}
27#endif
28
29int countWords(std::string input)
30{
31 const char *str = input.c_str();
32
33 bool inSpaces = true;
34 int numWords = 0;
35
36 while (*str != '\0') {
37 if (std::isspace(static_cast<unsigned char>(*str))) {
38 inSpaces = true;
39 } else if (inSpaces) {
40 numWords++;
41 inSpaces = false;
42 }
43 ++str;
44 }
45 return numWords;
46}
47
48} // Anonymous namespace
49
50struct TranslationInput {
51 std::string text;
52#if 0
53 slimt::ResponseOptions options;
54#endif
55};
56
57struct ModelDescription {
58 std::string config_file;
59 BergamotEngineUtils::SettingsInfo settings;
60};
61
62constexpr const size_t kTranslationCacheSize = 1 << 16;
63
64BergamotMarianInterface::BergamotMarianInterface(QObject *parent)
65 : QObject{parent}
66 , mPendingInput(nullptr)
67 , mPendingModel(nullptr)
68{
69#if 0
70 // This worker is the only thread that can interact with Marian. Right now
71 // it basically uses slimt::Service's non-blocking interface
72 // in a blocking way to have an easy way to control how what the next
73 // task will be, and to not start queueing up already irrelevant
74 // translation operations.
75 // This worker basically processes a command queue, except that there are
76 // only two possible commands: load model & translate input. And there are
77 // no actual queues because we always want the last command: we don't care
78 // about previously pending models or translations. The semaphore
79 // indicates whether there are 0, 1, or 2 commands pending. If a command
80 // is pending but both "queues" are empty, we'll treat that as a shutdown
81 // request.
82 mWorke = std::thread([&]() {
83 std::unique_ptr<slimt::AsyncService> service;
84 std::shared_ptr<slimt::TranslationModel> model;
85
86 std::mutex internal_mutex;
87
88 while (true) {
89 std::unique_ptr<ModelDescription> modelChange;
90 std::unique_ptr<TranslationInput> input;
91
92 {
93 // Wait for work
94 std::unique_lock<std::mutex> lock(mMutex);
95 mConditionVariable.wait(lock, [&] {
96 return mPendingModel || mPendingInput || mPendingShutdown;
97 });
98
99 // First check whether the command is loading a new model
100 if (mPendingModel)
101 modelChange = std::move(mPendingModel);
102
103 // Second check whether command is translating something.
104 // Note: else if because we only process one command per
105 // iteration otherwise commandIssued_ would go out of sync.
106 else if (mPendingInput)
107 input = std::move(mPendingInput);
108
109 // Command without any pending change -> poison.
110 else
111 break;
112 }
113
114 Q_EMIT pendingChanged(true);
115
116 try {
117 if (modelChange) {
118 // Reconstruct the service because cpu_threads might have changed.
119 // @TODO: don't recreate Service if cpu_threads didn't change?
120 slimt::AsyncService::Config serviceConfig;
121 serviceConfig.numWorkers = modelChange->settings.numberOfThread;
122 serviceConfig.cacheSize = modelChange->settings.useLocalCache ? kTranslationCacheSize : 0;
123
124 // Free up old service first (see https://github.com/browsermt/bergamot-translator/issues/290)
125 // Calling clear to remove any pending translations so we
126 // do not have to wait for those when AsyncService is destroyed.
127 service.reset();
128
129 service = std::make_unique<slimt::AsyncService>(serviceConfig);
130
131 // Initialise a new model. Old model will be released if
132 // service is done with it, which it is since all translation
133 // requests are effectively blocking in this thread.
134 auto modelConfig = makeOptions(modelChange->config_file, modelChange->settings);
135 model = std::make_shared<slimt::TranslationModel>(modelConfig, modelChange->settings.numberOfThread);
136 } else if (input) {
137 if (model) {
138 std::future<int> wordCount = std::async(
139 countWords,
140 input->text); // @TODO we're doing an "unnecessary" string copy here (necessary because we std::move input into service->translate)
141
142 Translation translation;
143
144 // Measure the time it takes to queue and respond to the
145 // translation request
146 service->translate(
147 model,
148 std::move(input->text),
149 [&](auto &&val) {
150 // Calculate translation speed in terms of words per second
151 std::unique_lock<std::mutex> lock(internal_mutex);
152 translation = Translation(std::move(val));
153 mConditionVariable.notify_one();
154 },
155 input->options);
156
157 // Wait for either translate lambda to call back, or a reason to cancel
158 std::unique_lock<std::mutex> lock(internal_mutex);
159 mConditionVariable.wait(lock, [&] {
160 return translation || mPendingShutdown || mPendingModel;
161 });
162
163 if (translation)
164 Q_EMIT translationReady(translation);
165 else
166 service->clear(); // translation was interrupted. Clear pending batches
167 // now to free any references to things that will go
168 // out of scope.
169 } else {
170 // TODO: What? Raise error? Set model_ to ""?
171 }
172 }
173 } catch (const std::runtime_error &e) {
174 Q_EMIT errorText(QString::fromStdString(e.what()));
175 }
176
177 Q_EMIT pendingChanged(false);
178 }
179 });
180#endif
181}
182
183BergamotMarianInterface::~BergamotMarianInterface()
184{
185#if 0
186 // Remove all pending changes and unlock worker (which will then break.)
187 {
188 std::unique_lock<std::mutex> lock(mMutex);
189
190 mPendingShutdown = true;
191 mPendingModel.reset();
192 mPendingInput.reset();
193
194 mConditionVariable.notify_one();
195 }
196
197 // Wait for worker to join as it depends on resources we still own.
198 mWorke.join();
199#endif
200}
201
202void BergamotMarianInterface::translate(const QString &str)
203{
204#if 0
205 // If we don't have a model yet (loaded, or queued to be loaded, doesn't matter)
206 // then don't bother trying to translate something.
207 if (mModelString.isEmpty()) {
208 qCWarning(TRANSLATOR_LIBBERGAMOT_LOG) << " mModelString is not defined!!!";
209 Q_EMIT errorText(i18n("Language model is not defined."));
210 return;
211 }
212
213 std::unique_lock<std::mutex> lock(mMutex);
214 std::unique_ptr<TranslationInput> input(new TranslationInput{str.toStdString(), slimt::ResponseOptions{}});
215 input->options.alignment = true;
216 input->options.HTML = false;
217
218 std::swap(mPendingInput, input);
219
220 mConditionVariable.notify_one();
221#endif
222}
223
224QString BergamotMarianInterface::model() const
225{
226 return mModelString;
227}
228
229void BergamotMarianInterface::setModel(const QString &pathModelDir, const BergamotEngineUtils::SettingsInfo &settings)
230{
231 mModelString = pathModelDir;
232
233 // Empty model string means just "unload" the model. We don't do that (yet),
234 // instead this just causes translate(QString) to no longer work.
235 if (mModelString.isEmpty())
236 return;
237
238#if 0
239 // move my shared_ptr from stack to heap
240 std::unique_lock<std::mutex> lock(mMutex);
241 std::unique_ptr<ModelDescription> model(new ModelDescription{mModelString.toStdString(), settings});
242 std::swap(mPendingModel, model);
243
244 // notify worker if there wasn't already a pending model
245 mConditionVariable.notify_one();
246#endif
247}
248
249#include "moc_bergamotmarianinterface.cpp"
Wrapper around a translation response from the bergamot service.
Definition translation.h:27
QString i18n(const char *text, const TYPE &arg...)
Q_EMITQ_EMIT
QString fromStdString(const std::string &str)
bool isEmpty() const const
std::string toStdString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:51:28 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.