blob: 789dfbb9266bbe8dd77ca4fd84c899ca86499221 [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>
Honglin Yu20391cd2020-10-27 15:58:49 +110015#include <base/files/memory_mapped_file.h>
Michael Martis8783c8e2019-06-26 17:30:54 +100016#include <tensorflow/lite/model.h>
Honglin Yuf33dce32019-12-05 15:10:39 +110017#include <unicode/putil.h>
18#include <unicode/udata.h>
19#include <utils/memory/mmap.h>
Michael Martisa74af932018-08-13 16:52:36 +100020
charleszhao17777f92020-04-23 12:53:11 +100021#include "ml/handwriting.h"
22#include "ml/handwriting_recognizer_impl.h"
Michael Martisa74af932018-08-13 16:52:36 +100023#include "ml/model_impl.h"
charleszhao17777f92020-04-23 12:53:11 +100024#include "ml/mojom/handwriting_recognizer.mojom.h"
Hidehiko Abeaa488c32018-08-31 23:49:41 +090025#include "ml/mojom/model.mojom.h"
Honglin Yud2204272020-08-26 14:21:37 +100026#include "ml/mojom/soda.mojom.h"
27#include "ml/soda_recognizer_impl.h"
Honglin Yuf33dce32019-12-05 15:10:39 +110028#include "ml/text_classifier_impl.h"
Michael Martisa74af932018-08-13 16:52:36 +100029
Andrew Moylanff6be512018-07-03 11:05:01 +100030namespace ml {
31
Michael Martisa74af932018-08-13 16:52:36 +100032namespace {
33
Honglin Yu0ed72352019-08-27 17:42:01 +100034using ::chromeos::machine_learning::mojom::BuiltinModelId;
35using ::chromeos::machine_learning::mojom::BuiltinModelSpecPtr;
36using ::chromeos::machine_learning::mojom::FlatBufferModelSpecPtr;
Andrew Moylanb481af72020-07-09 15:22:00 +100037using ::chromeos::machine_learning::mojom::HandwritingRecognizer;
charleszhao05c5a4a2020-06-09 16:49:54 +100038using ::chromeos::machine_learning::mojom::HandwritingRecognizerSpec;
39using ::chromeos::machine_learning::mojom::HandwritingRecognizerSpecPtr;
Charles Zhao6d467e62020-08-31 10:02:03 +100040using ::chromeos::machine_learning::mojom::LoadHandwritingModelResult;
Michael Martisa74af932018-08-13 16:52:36 +100041using ::chromeos::machine_learning::mojom::LoadModelResult;
Andrew Moylanb481af72020-07-09 15:22:00 +100042using ::chromeos::machine_learning::mojom::MachineLearningService;
43using ::chromeos::machine_learning::mojom::Model;
Honglin Yud2204272020-08-26 14:21:37 +100044using ::chromeos::machine_learning::mojom::SodaClient;
45using ::chromeos::machine_learning::mojom::SodaConfigPtr;
46using ::chromeos::machine_learning::mojom::SodaRecognizer;
Andrew Moylanb481af72020-07-09 15:22:00 +100047using ::chromeos::machine_learning::mojom::TextClassifier;
Michael Martisa74af932018-08-13 16:52:36 +100048
49constexpr char kSystemModelDir[] = "/opt/google/chrome/ml_models/";
Andrew Moylan79b34a42020-07-08 11:13:11 +100050// Base name for UMA metrics related to model loading (`LoadBuiltinModel`,
51// `LoadFlatBufferModel`, `LoadTextClassifier` or LoadHandwritingModel).
Honglin Yu6adafcd2019-07-22 13:48:11 +100052constexpr char kMetricsRequestName[] = "LoadModelResult";
Michael Martisa74af932018-08-13 16:52:36 +100053
Honglin Yuf33dce32019-12-05 15:10:39 +110054constexpr char kIcuDataFilePath[] = "/opt/google/chrome/icudtl.dat";
55
Honglin Yu20391cd2020-10-27 15:58:49 +110056// Used to hold the mmap object of the icu data file. Each process should only
57// have one instance of it. Intentionally never close it.
58// We can not make it as a member of `MachineLearningServiceImpl` because it
59// will crash the unit test (because in that case, when the
60// `MachineLearningServiceImpl` object is destructed, the file will be
61// unmapped but the icu data can not be reset in the testing process).
62base::MemoryMappedFile* g_icu_data_mmap_file = nullptr;
63
64void InitIcuIfNeeded() {
65 if (!g_icu_data_mmap_file) {
66 g_icu_data_mmap_file = new base::MemoryMappedFile();
67 CHECK(g_icu_data_mmap_file->Initialize(
68 base::FilePath(kIcuDataFilePath),
69 base::MemoryMappedFile::Access::READ_ONLY));
70 // Init the Icu library.
71 UErrorCode err = U_ZERO_ERROR;
72 udata_setCommonData(const_cast<uint8_t*>(g_icu_data_mmap_file->data()),
73 &err);
74 DCHECK(err == U_ZERO_ERROR);
75 // Never try to load Icu data from files.
76 udata_setFileAccess(UDATA_ONLY_PACKAGES, &err);
77 DCHECK(err == U_ZERO_ERROR);
78 }
79}
80
Michael Martisa74af932018-08-13 16:52:36 +100081} // namespace
82
Andrew Moylanff6be512018-07-03 11:05:01 +100083MachineLearningServiceImpl::MachineLearningServiceImpl(
Michael Martisa74af932018-08-13 16:52:36 +100084 mojo::ScopedMessagePipeHandle pipe,
Andrew Moylanb481af72020-07-09 15:22:00 +100085 base::Closure disconnect_handler,
Michael Martisa74af932018-08-13 16:52:36 +100086 const std::string& model_dir)
Honglin Yu20391cd2020-10-27 15:58:49 +110087 : builtin_model_metadata_(GetBuiltinModelMetadata()),
Michael Martisa74af932018-08-13 16:52:36 +100088 model_dir_(model_dir),
Andrew Moylanb481af72020-07-09 15:22:00 +100089 receiver_(this,
90 mojo::InterfaceRequest<
91 chromeos::machine_learning::mojom::MachineLearningService>(
92 std::move(pipe))) {
93 receiver_.set_disconnect_handler(std::move(disconnect_handler));
Andrew Moylanff6be512018-07-03 11:05:01 +100094}
95
Michael Martisa74af932018-08-13 16:52:36 +100096MachineLearningServiceImpl::MachineLearningServiceImpl(
Charles Zhaod4fb7b62020-08-25 17:21:58 +100097 mojo::ScopedMessagePipeHandle pipe,
98 base::Closure disconnect_handler,
99 dbus::Bus* bus)
Andrew Moylanb481af72020-07-09 15:22:00 +1000100 : MachineLearningServiceImpl(
Charles Zhaod4fb7b62020-08-25 17:21:58 +1000101 std::move(pipe), std::move(disconnect_handler), kSystemModelDir) {
102 if (bus) {
103 dlcservice_client_ = std::make_unique<DlcserviceClient>(bus);
104 }
105}
Michael Martisa74af932018-08-13 16:52:36 +1000106
Andrew Moylanb481af72020-07-09 15:22:00 +1000107void MachineLearningServiceImpl::Clone(
108 mojo::PendingReceiver<MachineLearningService> receiver) {
109 clone_receivers_.Add(this, std::move(receiver));
Andrew Moylan2fb80af2020-07-08 10:52:08 +1000110}
111
Honglin Yu0ed72352019-08-27 17:42:01 +1000112void MachineLearningServiceImpl::LoadBuiltinModel(
113 BuiltinModelSpecPtr spec,
Andrew Moylanb481af72020-07-09 15:22:00 +1000114 mojo::PendingReceiver<Model> receiver,
Qijiang Fan5d381a02020-04-19 23:42:37 +0900115 LoadBuiltinModelCallback callback) {
Honglin Yu0ed72352019-08-27 17:42:01 +1000116 // Unsupported models do not have metadata entries.
117 const auto metadata_lookup = builtin_model_metadata_.find(spec->id);
118 if (metadata_lookup == builtin_model_metadata_.end()) {
Honglin Yua81145a2019-09-23 15:20:13 +1000119 LOG(WARNING) << "LoadBuiltinModel requested for unsupported model ID "
120 << spec->id << ".";
Qijiang Fan5d381a02020-04-19 23:42:37 +0900121 std::move(callback).Run(LoadModelResult::MODEL_SPEC_ERROR);
Honglin Yu0ed72352019-08-27 17:42:01 +1000122 RecordModelSpecificationErrorEvent();
123 return;
124 }
125
126 const BuiltinModelMetadata& metadata = metadata_lookup->second;
127
128 DCHECK(!metadata.metrics_model_name.empty());
129
charleszhao5a7050e2020-07-14 15:21:41 +1000130 RequestMetrics request_metrics(metadata.metrics_model_name,
131 kMetricsRequestName);
Honglin Yu0ed72352019-08-27 17:42:01 +1000132 request_metrics.StartRecordingPerformanceMetrics();
133
134 // Attempt to load model.
135 const std::string model_path = model_dir_ + metadata.model_file;
136 std::unique_ptr<tflite::FlatBufferModel> model =
137 tflite::FlatBufferModel::BuildFromFile(model_path.c_str());
138 if (model == nullptr) {
139 LOG(ERROR) << "Failed to load model file '" << model_path << "'.";
Qijiang Fan5d381a02020-04-19 23:42:37 +0900140 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
Honglin Yu0ed72352019-08-27 17:42:01 +1000141 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
142 return;
143 }
144
Honglin Yuc0cef102020-01-17 15:26:01 +1100145 ModelImpl::Create(metadata.required_inputs, metadata.required_outputs,
Andrew Moylanb481af72020-07-09 15:22:00 +1000146 std::move(model), std::move(receiver),
Honglin Yuc0cef102020-01-17 15:26:01 +1100147 metadata.metrics_model_name);
Honglin Yu0ed72352019-08-27 17:42:01 +1000148
Qijiang Fan5d381a02020-04-19 23:42:37 +0900149 std::move(callback).Run(LoadModelResult::OK);
Honglin Yu0ed72352019-08-27 17:42:01 +1000150
151 request_metrics.FinishRecordingPerformanceMetrics();
152 request_metrics.RecordRequestEvent(LoadModelResult::OK);
153}
154
155void MachineLearningServiceImpl::LoadFlatBufferModel(
156 FlatBufferModelSpecPtr spec,
Andrew Moylanb481af72020-07-09 15:22:00 +1000157 mojo::PendingReceiver<Model> receiver,
Qijiang Fan5d381a02020-04-19 23:42:37 +0900158 LoadFlatBufferModelCallback callback) {
Honglin Yu0ed72352019-08-27 17:42:01 +1000159 DCHECK(!spec->metrics_model_name.empty());
160
charleszhao5a7050e2020-07-14 15:21:41 +1000161 RequestMetrics request_metrics(spec->metrics_model_name, kMetricsRequestName);
Honglin Yu0ed72352019-08-27 17:42:01 +1000162 request_metrics.StartRecordingPerformanceMetrics();
163
Andrew Moylan79b34a42020-07-08 11:13:11 +1000164 // Take the ownership of the content of `model_string` because `ModelImpl` has
Honglin Yu0ed72352019-08-27 17:42:01 +1000165 // to hold the memory.
166 auto model_string_impl =
167 std::make_unique<std::string>(std::move(spec->model_string));
168
169 std::unique_ptr<tflite::FlatBufferModel> model =
170 tflite::FlatBufferModel::BuildFromBuffer(model_string_impl->c_str(),
171 model_string_impl->length());
172 if (model == nullptr) {
173 LOG(ERROR) << "Failed to load model string of metric name: "
174 << spec->metrics_model_name << "'.";
Qijiang Fan5d381a02020-04-19 23:42:37 +0900175 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
Honglin Yu0ed72352019-08-27 17:42:01 +1000176 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
177 return;
178 }
179
Honglin Yuc0cef102020-01-17 15:26:01 +1100180 ModelImpl::Create(
Honglin Yu0ed72352019-08-27 17:42:01 +1000181 std::map<std::string, int>(spec->inputs.begin(), spec->inputs.end()),
182 std::map<std::string, int>(spec->outputs.begin(), spec->outputs.end()),
Andrew Moylanb481af72020-07-09 15:22:00 +1000183 std::move(model), std::move(model_string_impl), std::move(receiver),
Honglin Yu0ed72352019-08-27 17:42:01 +1000184 spec->metrics_model_name);
185
Qijiang Fan5d381a02020-04-19 23:42:37 +0900186 std::move(callback).Run(LoadModelResult::OK);
Honglin Yu0ed72352019-08-27 17:42:01 +1000187
188 request_metrics.FinishRecordingPerformanceMetrics();
189 request_metrics.RecordRequestEvent(LoadModelResult::OK);
190}
191
Honglin Yuf33dce32019-12-05 15:10:39 +1100192void MachineLearningServiceImpl::LoadTextClassifier(
Andrew Moylanb481af72020-07-09 15:22:00 +1000193 mojo::PendingReceiver<TextClassifier> receiver,
Honglin Yuf33dce32019-12-05 15:10:39 +1100194 LoadTextClassifierCallback callback) {
charleszhao5a7050e2020-07-14 15:21:41 +1000195 RequestMetrics request_metrics("TextClassifier", kMetricsRequestName);
Honglin Yuf33dce32019-12-05 15:10:39 +1100196 request_metrics.StartRecordingPerformanceMetrics();
197
Honglin Yuf33dce32019-12-05 15:10:39 +1100198 // Create the TextClassifier.
Honglin Yu3f99ff12020-10-15 00:40:11 +1100199 if (!TextClassifierImpl::Create(std::move(receiver))) {
Honglin Yuf33dce32019-12-05 15:10:39 +1100200 LOG(ERROR) << "Failed to create TextClassifierImpl object.";
201 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
202 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
203 return;
204 }
205
206 // initialize the icu library.
207 InitIcuIfNeeded();
208
209 std::move(callback).Run(LoadModelResult::OK);
210
211 request_metrics.FinishRecordingPerformanceMetrics();
212 request_metrics.RecordRequestEvent(LoadModelResult::OK);
213}
214
Charles Zhao6d467e62020-08-31 10:02:03 +1000215void LoadHandwritingModelFromDir(
216 HandwritingRecognizerSpecPtr spec,
217 mojo::PendingReceiver<HandwritingRecognizer> receiver,
218 MachineLearningServiceImpl::LoadHandwritingModelCallback callback,
219 const std::string& root_path) {
220 RequestMetrics request_metrics("HandwritingModel", kMetricsRequestName);
221 request_metrics.StartRecordingPerformanceMetrics();
222
223 // Returns error if root_path is empty.
224 if (root_path.empty()) {
225 std::move(callback).Run(LoadHandwritingModelResult::DLC_GET_PATH_ERROR);
226 request_metrics.RecordRequestEvent(
227 LoadHandwritingModelResult::DLC_GET_PATH_ERROR);
228 return;
229 }
230
231 // Load HandwritingLibrary.
232 auto* const hwr_library = ml::HandwritingLibrary::GetInstance(root_path);
233
234 if (hwr_library->GetStatus() != ml::HandwritingLibrary::Status::kOk) {
235 LOG(ERROR) << "Initialize ml::HandwritingLibrary with error "
236 << static_cast<int>(hwr_library->GetStatus());
237
238 switch (hwr_library->GetStatus()) {
239 case ml::HandwritingLibrary::Status::kLoadLibraryFailed: {
240 std::move(callback).Run(
241 LoadHandwritingModelResult::LOAD_NATIVE_LIB_ERROR);
242 request_metrics.RecordRequestEvent(
243 LoadHandwritingModelResult::LOAD_NATIVE_LIB_ERROR);
244 return;
245 }
246 case ml::HandwritingLibrary::Status::kFunctionLookupFailed: {
247 std::move(callback).Run(
248 LoadHandwritingModelResult::LOAD_FUNC_PTR_ERROR);
249 request_metrics.RecordRequestEvent(
250 LoadHandwritingModelResult::LOAD_FUNC_PTR_ERROR);
251 return;
252 }
253 default: {
254 std::move(callback).Run(LoadHandwritingModelResult::LOAD_MODEL_ERROR);
255 request_metrics.RecordRequestEvent(
256 LoadHandwritingModelResult::LOAD_MODEL_ERROR);
257 return;
258 }
259 }
260 }
261
262 // Create HandwritingRecognizer.
263 if (!HandwritingRecognizerImpl::Create(std::move(spec),
264 std::move(receiver))) {
265 LOG(ERROR) << "LoadHandwritingRecognizer returned false.";
266 std::move(callback).Run(LoadHandwritingModelResult::LOAD_MODEL_FILES_ERROR);
267 request_metrics.RecordRequestEvent(
268 LoadHandwritingModelResult::LOAD_MODEL_FILES_ERROR);
269 return;
270 }
271
272 std::move(callback).Run(LoadHandwritingModelResult::OK);
273 request_metrics.FinishRecordingPerformanceMetrics();
274 request_metrics.RecordRequestEvent(LoadHandwritingModelResult::OK);
275}
276
277void MachineLearningServiceImpl::LoadHandwritingModel(
278 chromeos::machine_learning::mojom::HandwritingRecognizerSpecPtr spec,
279 mojo::PendingReceiver<
280 chromeos::machine_learning::mojom::HandwritingRecognizer> receiver,
281 LoadHandwritingModelCallback callback) {
282 // If handwriting is installed on rootfs, load it from there.
283 if (ml::HandwritingLibrary::IsUseLibHandwritingEnabled()) {
284 LoadHandwritingModelFromDir(
285 std::move(spec), std::move(receiver), std::move(callback),
286 ml::HandwritingLibrary::kHandwritingDefaultModelDir);
287 return;
288 }
289
290 // If handwriting is installed as DLC, get the dir and subsequently load it
291 // from there.
292 if (ml::HandwritingLibrary::IsUseLibHandwritingDlcEnabled()) {
293 dlcservice_client_->GetDlcRootPath(
294 "libhandwriting",
295 base::BindOnce(&LoadHandwritingModelFromDir, std::move(spec),
296 std::move(receiver), std::move(callback)));
297 return;
298 }
299
300 // If handwriting is not on rootfs and not in DLC, this function should not
301 // be called.
302 LOG(ERROR) << "Calling LoadHandwritingModel without Handwriting enabled "
303 "should never happen.";
304 std::move(callback).Run(LoadHandwritingModelResult::LOAD_MODEL_ERROR);
charleszhao05c5a4a2020-06-09 16:49:54 +1000305}
306
307void MachineLearningServiceImpl::LoadHandwritingModelWithSpec(
308 HandwritingRecognizerSpecPtr spec,
Andrew Moylanb481af72020-07-09 15:22:00 +1000309 mojo::PendingReceiver<HandwritingRecognizer> receiver,
Charles Zhaoc882eb02020-07-27 10:02:35 +1000310 LoadHandwritingModelWithSpecCallback callback) {
charleszhao5a7050e2020-07-14 15:21:41 +1000311 RequestMetrics request_metrics("HandwritingModel", kMetricsRequestName);
charleszhao17777f92020-04-23 12:53:11 +1000312 request_metrics.StartRecordingPerformanceMetrics();
313
314 // Load HandwritingLibrary.
315 auto* const hwr_library = ml::HandwritingLibrary::GetInstance();
316
317 if (hwr_library->GetStatus() ==
318 ml::HandwritingLibrary::Status::kNotSupported) {
319 LOG(ERROR) << "Initialize ml::HandwritingLibrary with error "
320 << static_cast<int>(hwr_library->GetStatus());
321
322 std::move(callback).Run(LoadModelResult::FEATURE_NOT_SUPPORTED_ERROR);
323 request_metrics.RecordRequestEvent(
324 LoadModelResult::FEATURE_NOT_SUPPORTED_ERROR);
325 return;
326 }
327
328 if (hwr_library->GetStatus() != ml::HandwritingLibrary::Status::kOk) {
329 LOG(ERROR) << "Initialize ml::HandwritingLibrary with error "
330 << static_cast<int>(hwr_library->GetStatus());
331
332 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
333 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
334 return;
335 }
336
337 // Create HandwritingRecognizer.
Andrew Moylanb481af72020-07-09 15:22:00 +1000338 if (!HandwritingRecognizerImpl::Create(std::move(spec),
339 std::move(receiver))) {
charleszhao17777f92020-04-23 12:53:11 +1000340 LOG(ERROR) << "LoadHandwritingRecognizer returned false.";
341 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
342 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
343 return;
344 }
345
346 std::move(callback).Run(LoadModelResult::OK);
347 request_metrics.FinishRecordingPerformanceMetrics();
348 request_metrics.RecordRequestEvent(LoadModelResult::OK);
349}
350
Honglin Yud2204272020-08-26 14:21:37 +1000351void MachineLearningServiceImpl::LoadSpeechRecognizer(
352 SodaConfigPtr config,
353 mojo::PendingRemote<SodaClient> soda_client,
354 mojo::PendingReceiver<SodaRecognizer> soda_recognizer,
355 LoadSpeechRecognizerCallback callback) {
356 RequestMetrics request_metrics("Soda", kMetricsRequestName);
357 request_metrics.StartRecordingPerformanceMetrics();
358
359 // Create the SodaRecognizer.
360 if (!SodaRecognizerImpl::Create(std::move(config), std::move(soda_client),
361 std::move(soda_recognizer))) {
362 LOG(ERROR) << "Failed to create SodaRecognizerImpl object.";
363 // TODO(robsc): it may be better that SODA has its specific enum values to
364 // return, similar to handwriting. So before we finalize the impl of SODA
365 // Mojo API, we may revisit this return value.
366 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
367 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
368 return;
369 }
370
371 std::move(callback).Run(LoadModelResult::OK);
372
373 request_metrics.FinishRecordingPerformanceMetrics();
374 request_metrics.RecordRequestEvent(LoadModelResult::OK);
375}
376
Andrew Moylanff6be512018-07-03 11:05:01 +1000377} // namespace ml