blob: 39ff78d52fecdb45d986ec2e28b1b479d1dda194 [file] [log] [blame]
Andrew Moylanff6be512018-07-03 11:05:01 +10001// Copyright 2018 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ml/machine_learning_service_impl.h"
alanlxlcb1f8562018-11-01 15:16:11 +11006#include "ml/request_metrics.h"
Andrew Moylanff6be512018-07-03 11:05:01 +10007
Michael Martisa74af932018-08-13 16:52:36 +10008#include <memory>
Andrew Moylanff6be512018-07-03 11:05:01 +10009#include <utility>
10
Michael Martisa74af932018-08-13 16:52:36 +100011#include <base/bind.h>
12#include <base/bind_helpers.h>
Honglin Yuf33dce32019-12-05 15:10:39 +110013#include <base/files/file.h>
14#include <base/files/file_util.h>
Michael Martis8783c8e2019-06-26 17:30:54 +100015#include <tensorflow/lite/model.h>
Honglin Yuf33dce32019-12-05 15:10:39 +110016#include <unicode/putil.h>
17#include <unicode/udata.h>
18#include <utils/memory/mmap.h>
Michael Martisa74af932018-08-13 16:52:36 +100019
charleszhao17777f92020-04-23 12:53:11 +100020#include "ml/handwriting.h"
21#include "ml/handwriting_recognizer_impl.h"
Michael Martisa74af932018-08-13 16:52:36 +100022#include "ml/model_impl.h"
charleszhao17777f92020-04-23 12:53:11 +100023#include "ml/mojom/handwriting_recognizer.mojom.h"
Hidehiko Abeaa488c32018-08-31 23:49:41 +090024#include "ml/mojom/model.mojom.h"
Honglin Yud2204272020-08-26 14:21:37 +100025#include "ml/mojom/soda.mojom.h"
26#include "ml/soda_recognizer_impl.h"
Honglin Yuf33dce32019-12-05 15:10:39 +110027#include "ml/text_classifier_impl.h"
Michael Martisa74af932018-08-13 16:52:36 +100028
Andrew Moylanff6be512018-07-03 11:05:01 +100029namespace ml {
30
Michael Martisa74af932018-08-13 16:52:36 +100031namespace {
32
Honglin Yu0ed72352019-08-27 17:42:01 +100033using ::chromeos::machine_learning::mojom::BuiltinModelId;
34using ::chromeos::machine_learning::mojom::BuiltinModelSpecPtr;
35using ::chromeos::machine_learning::mojom::FlatBufferModelSpecPtr;
Andrew Moylanb481af72020-07-09 15:22:00 +100036using ::chromeos::machine_learning::mojom::HandwritingRecognizer;
charleszhao05c5a4a2020-06-09 16:49:54 +100037using ::chromeos::machine_learning::mojom::HandwritingRecognizerSpec;
38using ::chromeos::machine_learning::mojom::HandwritingRecognizerSpecPtr;
Charles Zhao6d467e62020-08-31 10:02:03 +100039using ::chromeos::machine_learning::mojom::LoadHandwritingModelResult;
Michael Martisa74af932018-08-13 16:52:36 +100040using ::chromeos::machine_learning::mojom::LoadModelResult;
Andrew Moylanb481af72020-07-09 15:22:00 +100041using ::chromeos::machine_learning::mojom::MachineLearningService;
42using ::chromeos::machine_learning::mojom::Model;
Honglin Yud2204272020-08-26 14:21:37 +100043using ::chromeos::machine_learning::mojom::SodaClient;
44using ::chromeos::machine_learning::mojom::SodaConfigPtr;
45using ::chromeos::machine_learning::mojom::SodaRecognizer;
Andrew Moylanb481af72020-07-09 15:22:00 +100046using ::chromeos::machine_learning::mojom::TextClassifier;
Michael Martisa74af932018-08-13 16:52:36 +100047
48constexpr char kSystemModelDir[] = "/opt/google/chrome/ml_models/";
Andrew Moylan79b34a42020-07-08 11:13:11 +100049// Base name for UMA metrics related to model loading (`LoadBuiltinModel`,
50// `LoadFlatBufferModel`, `LoadTextClassifier` or LoadHandwritingModel).
Honglin Yu6adafcd2019-07-22 13:48:11 +100051constexpr char kMetricsRequestName[] = "LoadModelResult";
Michael Martisa74af932018-08-13 16:52:36 +100052
Honglin Yuf33dce32019-12-05 15:10:39 +110053constexpr char kTextClassifierModelFile[] =
Honglin Yu0dc07132020-09-30 09:43:59 +100054 "mlservice-model-text_classifier_en-v711.fb";
Honglin Yuf33dce32019-12-05 15:10:39 +110055
Honglin Yuc5100022020-07-09 11:54:27 +100056constexpr char kLanguageIdentificationModelFile[] =
57 "mlservice-model-language_identification-20190924.smfb";
58
Honglin Yuf33dce32019-12-05 15:10:39 +110059constexpr char kIcuDataFilePath[] = "/opt/google/chrome/icudtl.dat";
60
Michael Martisa74af932018-08-13 16:52:36 +100061} // namespace
62
Andrew Moylanff6be512018-07-03 11:05:01 +100063MachineLearningServiceImpl::MachineLearningServiceImpl(
Michael Martisa74af932018-08-13 16:52:36 +100064 mojo::ScopedMessagePipeHandle pipe,
Andrew Moylanb481af72020-07-09 15:22:00 +100065 base::Closure disconnect_handler,
Michael Martisa74af932018-08-13 16:52:36 +100066 const std::string& model_dir)
Honglin Yuf33dce32019-12-05 15:10:39 +110067 : icu_data_(nullptr),
68 text_classifier_model_filename_(kTextClassifierModelFile),
69 builtin_model_metadata_(GetBuiltinModelMetadata()),
Michael Martisa74af932018-08-13 16:52:36 +100070 model_dir_(model_dir),
Andrew Moylanb481af72020-07-09 15:22:00 +100071 receiver_(this,
72 mojo::InterfaceRequest<
73 chromeos::machine_learning::mojom::MachineLearningService>(
74 std::move(pipe))) {
75 receiver_.set_disconnect_handler(std::move(disconnect_handler));
Andrew Moylanff6be512018-07-03 11:05:01 +100076}
77
Michael Martisa74af932018-08-13 16:52:36 +100078MachineLearningServiceImpl::MachineLearningServiceImpl(
Charles Zhaod4fb7b62020-08-25 17:21:58 +100079 mojo::ScopedMessagePipeHandle pipe,
80 base::Closure disconnect_handler,
81 dbus::Bus* bus)
Andrew Moylanb481af72020-07-09 15:22:00 +100082 : MachineLearningServiceImpl(
Charles Zhaod4fb7b62020-08-25 17:21:58 +100083 std::move(pipe), std::move(disconnect_handler), kSystemModelDir) {
84 if (bus) {
85 dlcservice_client_ = std::make_unique<DlcserviceClient>(bus);
86 }
87}
Michael Martisa74af932018-08-13 16:52:36 +100088
Honglin Yuf33dce32019-12-05 15:10:39 +110089void MachineLearningServiceImpl::SetTextClassifierModelFilenameForTesting(
90 const std::string& filename) {
91 text_classifier_model_filename_ = filename;
92}
93
Andrew Moylanb481af72020-07-09 15:22:00 +100094void MachineLearningServiceImpl::Clone(
95 mojo::PendingReceiver<MachineLearningService> receiver) {
96 clone_receivers_.Add(this, std::move(receiver));
Andrew Moylan2fb80af2020-07-08 10:52:08 +100097}
98
Honglin Yu0ed72352019-08-27 17:42:01 +100099void MachineLearningServiceImpl::LoadBuiltinModel(
100 BuiltinModelSpecPtr spec,
Andrew Moylanb481af72020-07-09 15:22:00 +1000101 mojo::PendingReceiver<Model> receiver,
Qijiang Fan5d381a02020-04-19 23:42:37 +0900102 LoadBuiltinModelCallback callback) {
Honglin Yu0ed72352019-08-27 17:42:01 +1000103 // Unsupported models do not have metadata entries.
104 const auto metadata_lookup = builtin_model_metadata_.find(spec->id);
105 if (metadata_lookup == builtin_model_metadata_.end()) {
Honglin Yua81145a2019-09-23 15:20:13 +1000106 LOG(WARNING) << "LoadBuiltinModel requested for unsupported model ID "
107 << spec->id << ".";
Qijiang Fan5d381a02020-04-19 23:42:37 +0900108 std::move(callback).Run(LoadModelResult::MODEL_SPEC_ERROR);
Honglin Yu0ed72352019-08-27 17:42:01 +1000109 RecordModelSpecificationErrorEvent();
110 return;
111 }
112
113 const BuiltinModelMetadata& metadata = metadata_lookup->second;
114
115 DCHECK(!metadata.metrics_model_name.empty());
116
charleszhao5a7050e2020-07-14 15:21:41 +1000117 RequestMetrics request_metrics(metadata.metrics_model_name,
118 kMetricsRequestName);
Honglin Yu0ed72352019-08-27 17:42:01 +1000119 request_metrics.StartRecordingPerformanceMetrics();
120
121 // Attempt to load model.
122 const std::string model_path = model_dir_ + metadata.model_file;
123 std::unique_ptr<tflite::FlatBufferModel> model =
124 tflite::FlatBufferModel::BuildFromFile(model_path.c_str());
125 if (model == nullptr) {
126 LOG(ERROR) << "Failed to load model file '" << model_path << "'.";
Qijiang Fan5d381a02020-04-19 23:42:37 +0900127 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
Honglin Yu0ed72352019-08-27 17:42:01 +1000128 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
129 return;
130 }
131
Honglin Yuc0cef102020-01-17 15:26:01 +1100132 ModelImpl::Create(metadata.required_inputs, metadata.required_outputs,
Andrew Moylanb481af72020-07-09 15:22:00 +1000133 std::move(model), std::move(receiver),
Honglin Yuc0cef102020-01-17 15:26:01 +1100134 metadata.metrics_model_name);
Honglin Yu0ed72352019-08-27 17:42:01 +1000135
Qijiang Fan5d381a02020-04-19 23:42:37 +0900136 std::move(callback).Run(LoadModelResult::OK);
Honglin Yu0ed72352019-08-27 17:42:01 +1000137
138 request_metrics.FinishRecordingPerformanceMetrics();
139 request_metrics.RecordRequestEvent(LoadModelResult::OK);
140}
141
142void MachineLearningServiceImpl::LoadFlatBufferModel(
143 FlatBufferModelSpecPtr spec,
Andrew Moylanb481af72020-07-09 15:22:00 +1000144 mojo::PendingReceiver<Model> receiver,
Qijiang Fan5d381a02020-04-19 23:42:37 +0900145 LoadFlatBufferModelCallback callback) {
Honglin Yu0ed72352019-08-27 17:42:01 +1000146 DCHECK(!spec->metrics_model_name.empty());
147
charleszhao5a7050e2020-07-14 15:21:41 +1000148 RequestMetrics request_metrics(spec->metrics_model_name, kMetricsRequestName);
Honglin Yu0ed72352019-08-27 17:42:01 +1000149 request_metrics.StartRecordingPerformanceMetrics();
150
Andrew Moylan79b34a42020-07-08 11:13:11 +1000151 // Take the ownership of the content of `model_string` because `ModelImpl` has
Honglin Yu0ed72352019-08-27 17:42:01 +1000152 // to hold the memory.
153 auto model_string_impl =
154 std::make_unique<std::string>(std::move(spec->model_string));
155
156 std::unique_ptr<tflite::FlatBufferModel> model =
157 tflite::FlatBufferModel::BuildFromBuffer(model_string_impl->c_str(),
158 model_string_impl->length());
159 if (model == nullptr) {
160 LOG(ERROR) << "Failed to load model string of metric name: "
161 << spec->metrics_model_name << "'.";
Qijiang Fan5d381a02020-04-19 23:42:37 +0900162 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
Honglin Yu0ed72352019-08-27 17:42:01 +1000163 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
164 return;
165 }
166
Honglin Yuc0cef102020-01-17 15:26:01 +1100167 ModelImpl::Create(
Honglin Yu0ed72352019-08-27 17:42:01 +1000168 std::map<std::string, int>(spec->inputs.begin(), spec->inputs.end()),
169 std::map<std::string, int>(spec->outputs.begin(), spec->outputs.end()),
Andrew Moylanb481af72020-07-09 15:22:00 +1000170 std::move(model), std::move(model_string_impl), std::move(receiver),
Honglin Yu0ed72352019-08-27 17:42:01 +1000171 spec->metrics_model_name);
172
Qijiang Fan5d381a02020-04-19 23:42:37 +0900173 std::move(callback).Run(LoadModelResult::OK);
Honglin Yu0ed72352019-08-27 17:42:01 +1000174
175 request_metrics.FinishRecordingPerformanceMetrics();
176 request_metrics.RecordRequestEvent(LoadModelResult::OK);
177}
178
Honglin Yuf33dce32019-12-05 15:10:39 +1100179void MachineLearningServiceImpl::LoadTextClassifier(
Andrew Moylanb481af72020-07-09 15:22:00 +1000180 mojo::PendingReceiver<TextClassifier> receiver,
Honglin Yuf33dce32019-12-05 15:10:39 +1100181 LoadTextClassifierCallback callback) {
charleszhao5a7050e2020-07-14 15:21:41 +1000182 RequestMetrics request_metrics("TextClassifier", kMetricsRequestName);
Honglin Yuf33dce32019-12-05 15:10:39 +1100183 request_metrics.StartRecordingPerformanceMetrics();
184
185 // Attempt to load model.
186 std::string model_path = model_dir_ + text_classifier_model_filename_;
187 auto scoped_mmap =
188 std::make_unique<libtextclassifier3::ScopedMmap>(model_path);
189 if (!scoped_mmap->handle().ok()) {
190 LOG(ERROR) << "Failed to load the text classifier model file '"
191 << model_path << "'.";
192 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
193 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
194 return;
195 }
196
197 // Create the TextClassifier.
Honglin Yuc5100022020-07-09 11:54:27 +1000198 if (!TextClassifierImpl::Create(&scoped_mmap,
199 model_dir_ + kLanguageIdentificationModelFile,
Andrew Moylanb481af72020-07-09 15:22:00 +1000200 std::move(receiver))) {
Honglin Yuf33dce32019-12-05 15:10:39 +1100201 LOG(ERROR) << "Failed to create TextClassifierImpl object.";
202 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
203 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
204 return;
205 }
206
207 // initialize the icu library.
208 InitIcuIfNeeded();
209
210 std::move(callback).Run(LoadModelResult::OK);
211
212 request_metrics.FinishRecordingPerformanceMetrics();
213 request_metrics.RecordRequestEvent(LoadModelResult::OK);
214}
215
Charles Zhao6d467e62020-08-31 10:02:03 +1000216void LoadHandwritingModelFromDir(
217 HandwritingRecognizerSpecPtr spec,
218 mojo::PendingReceiver<HandwritingRecognizer> receiver,
219 MachineLearningServiceImpl::LoadHandwritingModelCallback callback,
220 const std::string& root_path) {
221 RequestMetrics request_metrics("HandwritingModel", kMetricsRequestName);
222 request_metrics.StartRecordingPerformanceMetrics();
223
224 // Returns error if root_path is empty.
225 if (root_path.empty()) {
226 std::move(callback).Run(LoadHandwritingModelResult::DLC_GET_PATH_ERROR);
227 request_metrics.RecordRequestEvent(
228 LoadHandwritingModelResult::DLC_GET_PATH_ERROR);
229 return;
230 }
231
232 // Load HandwritingLibrary.
233 auto* const hwr_library = ml::HandwritingLibrary::GetInstance(root_path);
234
235 if (hwr_library->GetStatus() != ml::HandwritingLibrary::Status::kOk) {
236 LOG(ERROR) << "Initialize ml::HandwritingLibrary with error "
237 << static_cast<int>(hwr_library->GetStatus());
238
239 switch (hwr_library->GetStatus()) {
240 case ml::HandwritingLibrary::Status::kLoadLibraryFailed: {
241 std::move(callback).Run(
242 LoadHandwritingModelResult::LOAD_NATIVE_LIB_ERROR);
243 request_metrics.RecordRequestEvent(
244 LoadHandwritingModelResult::LOAD_NATIVE_LIB_ERROR);
245 return;
246 }
247 case ml::HandwritingLibrary::Status::kFunctionLookupFailed: {
248 std::move(callback).Run(
249 LoadHandwritingModelResult::LOAD_FUNC_PTR_ERROR);
250 request_metrics.RecordRequestEvent(
251 LoadHandwritingModelResult::LOAD_FUNC_PTR_ERROR);
252 return;
253 }
254 default: {
255 std::move(callback).Run(LoadHandwritingModelResult::LOAD_MODEL_ERROR);
256 request_metrics.RecordRequestEvent(
257 LoadHandwritingModelResult::LOAD_MODEL_ERROR);
258 return;
259 }
260 }
261 }
262
263 // Create HandwritingRecognizer.
264 if (!HandwritingRecognizerImpl::Create(std::move(spec),
265 std::move(receiver))) {
266 LOG(ERROR) << "LoadHandwritingRecognizer returned false.";
267 std::move(callback).Run(LoadHandwritingModelResult::LOAD_MODEL_FILES_ERROR);
268 request_metrics.RecordRequestEvent(
269 LoadHandwritingModelResult::LOAD_MODEL_FILES_ERROR);
270 return;
271 }
272
273 std::move(callback).Run(LoadHandwritingModelResult::OK);
274 request_metrics.FinishRecordingPerformanceMetrics();
275 request_metrics.RecordRequestEvent(LoadHandwritingModelResult::OK);
276}
277
278void MachineLearningServiceImpl::LoadHandwritingModel(
279 chromeos::machine_learning::mojom::HandwritingRecognizerSpecPtr spec,
280 mojo::PendingReceiver<
281 chromeos::machine_learning::mojom::HandwritingRecognizer> receiver,
282 LoadHandwritingModelCallback callback) {
283 // If handwriting is installed on rootfs, load it from there.
284 if (ml::HandwritingLibrary::IsUseLibHandwritingEnabled()) {
285 LoadHandwritingModelFromDir(
286 std::move(spec), std::move(receiver), std::move(callback),
287 ml::HandwritingLibrary::kHandwritingDefaultModelDir);
288 return;
289 }
290
291 // If handwriting is installed as DLC, get the dir and subsequently load it
292 // from there.
293 if (ml::HandwritingLibrary::IsUseLibHandwritingDlcEnabled()) {
294 dlcservice_client_->GetDlcRootPath(
295 "libhandwriting",
296 base::BindOnce(&LoadHandwritingModelFromDir, std::move(spec),
297 std::move(receiver), std::move(callback)));
298 return;
299 }
300
301 // If handwriting is not on rootfs and not in DLC, this function should not
302 // be called.
303 LOG(ERROR) << "Calling LoadHandwritingModel without Handwriting enabled "
304 "should never happen.";
305 std::move(callback).Run(LoadHandwritingModelResult::LOAD_MODEL_ERROR);
charleszhao05c5a4a2020-06-09 16:49:54 +1000306}
307
308void MachineLearningServiceImpl::LoadHandwritingModelWithSpec(
309 HandwritingRecognizerSpecPtr spec,
Andrew Moylanb481af72020-07-09 15:22:00 +1000310 mojo::PendingReceiver<HandwritingRecognizer> receiver,
Charles Zhaoc882eb02020-07-27 10:02:35 +1000311 LoadHandwritingModelWithSpecCallback callback) {
charleszhao5a7050e2020-07-14 15:21:41 +1000312 RequestMetrics request_metrics("HandwritingModel", kMetricsRequestName);
charleszhao17777f92020-04-23 12:53:11 +1000313 request_metrics.StartRecordingPerformanceMetrics();
314
315 // Load HandwritingLibrary.
316 auto* const hwr_library = ml::HandwritingLibrary::GetInstance();
317
318 if (hwr_library->GetStatus() ==
319 ml::HandwritingLibrary::Status::kNotSupported) {
320 LOG(ERROR) << "Initialize ml::HandwritingLibrary with error "
321 << static_cast<int>(hwr_library->GetStatus());
322
323 std::move(callback).Run(LoadModelResult::FEATURE_NOT_SUPPORTED_ERROR);
324 request_metrics.RecordRequestEvent(
325 LoadModelResult::FEATURE_NOT_SUPPORTED_ERROR);
326 return;
327 }
328
329 if (hwr_library->GetStatus() != ml::HandwritingLibrary::Status::kOk) {
330 LOG(ERROR) << "Initialize ml::HandwritingLibrary with error "
331 << static_cast<int>(hwr_library->GetStatus());
332
333 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
334 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
335 return;
336 }
337
338 // Create HandwritingRecognizer.
Andrew Moylanb481af72020-07-09 15:22:00 +1000339 if (!HandwritingRecognizerImpl::Create(std::move(spec),
340 std::move(receiver))) {
charleszhao17777f92020-04-23 12:53:11 +1000341 LOG(ERROR) << "LoadHandwritingRecognizer returned false.";
342 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
343 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
344 return;
345 }
346
347 std::move(callback).Run(LoadModelResult::OK);
348 request_metrics.FinishRecordingPerformanceMetrics();
349 request_metrics.RecordRequestEvent(LoadModelResult::OK);
350}
351
Honglin Yud2204272020-08-26 14:21:37 +1000352void MachineLearningServiceImpl::LoadSpeechRecognizer(
353 SodaConfigPtr config,
354 mojo::PendingRemote<SodaClient> soda_client,
355 mojo::PendingReceiver<SodaRecognizer> soda_recognizer,
356 LoadSpeechRecognizerCallback callback) {
357 RequestMetrics request_metrics("Soda", kMetricsRequestName);
358 request_metrics.StartRecordingPerformanceMetrics();
359
360 // Create the SodaRecognizer.
361 if (!SodaRecognizerImpl::Create(std::move(config), std::move(soda_client),
362 std::move(soda_recognizer))) {
363 LOG(ERROR) << "Failed to create SodaRecognizerImpl object.";
364 // TODO(robsc): it may be better that SODA has its specific enum values to
365 // return, similar to handwriting. So before we finalize the impl of SODA
366 // Mojo API, we may revisit this return value.
367 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
368 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
369 return;
370 }
371
372 std::move(callback).Run(LoadModelResult::OK);
373
374 request_metrics.FinishRecordingPerformanceMetrics();
375 request_metrics.RecordRequestEvent(LoadModelResult::OK);
376}
377
Honglin Yuf33dce32019-12-05 15:10:39 +1100378void MachineLearningServiceImpl::InitIcuIfNeeded() {
379 if (icu_data_ == nullptr) {
380 // Need to load the data file again.
381 int64_t file_size;
382 const base::FilePath icu_data_file_path(kIcuDataFilePath);
383 CHECK(base::GetFileSize(icu_data_file_path, &file_size));
384 icu_data_ = new char[file_size];
385 CHECK(base::ReadFile(icu_data_file_path, icu_data_,
386 static_cast<int>(file_size)) == file_size);
387 // Init the Icu library.
388 UErrorCode err = U_ZERO_ERROR;
389 udata_setCommonData(reinterpret_cast<void*>(icu_data_), &err);
390 DCHECK(err == U_ZERO_ERROR);
391 // Never try to load Icu data from files.
392 udata_setFileAccess(UDATA_ONLY_PACKAGES, &err);
393 }
394}
395
Andrew Moylanff6be512018-07-03 11:05:01 +1000396} // namespace ml