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

KDE's Doxygen guidelines are available online.