blob: 9987a2f24d84d3ef56d7f7eb40cd6c926ae46c75 [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 Yuf33dce32019-12-05 15:10:39 +110025#include "ml/text_classifier_impl.h"
Michael Martisa74af932018-08-13 16:52:36 +100026
Andrew Moylanff6be512018-07-03 11:05:01 +100027namespace ml {
28
Michael Martisa74af932018-08-13 16:52:36 +100029namespace {
30
Honglin Yu0ed72352019-08-27 17:42:01 +100031using ::chromeos::machine_learning::mojom::BuiltinModelId;
32using ::chromeos::machine_learning::mojom::BuiltinModelSpecPtr;
33using ::chromeos::machine_learning::mojom::FlatBufferModelSpecPtr;
Andrew Moylanb481af72020-07-09 15:22:00 +100034using ::chromeos::machine_learning::mojom::HandwritingRecognizer;
charleszhao05c5a4a2020-06-09 16:49:54 +100035using ::chromeos::machine_learning::mojom::HandwritingRecognizerSpec;
36using ::chromeos::machine_learning::mojom::HandwritingRecognizerSpecPtr;
Charles Zhao6d467e62020-08-31 10:02:03 +100037using ::chromeos::machine_learning::mojom::LoadHandwritingModelResult;
Michael Martisa74af932018-08-13 16:52:36 +100038using ::chromeos::machine_learning::mojom::LoadModelResult;
Andrew Moylanb481af72020-07-09 15:22:00 +100039using ::chromeos::machine_learning::mojom::MachineLearningService;
40using ::chromeos::machine_learning::mojom::Model;
41using ::chromeos::machine_learning::mojom::TextClassifier;
Michael Martisa74af932018-08-13 16:52:36 +100042
43constexpr char kSystemModelDir[] = "/opt/google/chrome/ml_models/";
Andrew Moylan79b34a42020-07-08 11:13:11 +100044// Base name for UMA metrics related to model loading (`LoadBuiltinModel`,
45// `LoadFlatBufferModel`, `LoadTextClassifier` or LoadHandwritingModel).
Honglin Yu6adafcd2019-07-22 13:48:11 +100046constexpr char kMetricsRequestName[] = "LoadModelResult";
Michael Martisa74af932018-08-13 16:52:36 +100047
Honglin Yuf33dce32019-12-05 15:10:39 +110048constexpr char kTextClassifierModelFile[] =
49 "mlservice-model-text_classifier_en-v706.fb";
50
Honglin Yuc5100022020-07-09 11:54:27 +100051constexpr char kLanguageIdentificationModelFile[] =
52 "mlservice-model-language_identification-20190924.smfb";
53
Honglin Yuf33dce32019-12-05 15:10:39 +110054constexpr char kIcuDataFilePath[] = "/opt/google/chrome/icudtl.dat";
55
Michael Martisa74af932018-08-13 16:52:36 +100056} // namespace
57
Andrew Moylanff6be512018-07-03 11:05:01 +100058MachineLearningServiceImpl::MachineLearningServiceImpl(
Michael Martisa74af932018-08-13 16:52:36 +100059 mojo::ScopedMessagePipeHandle pipe,
Andrew Moylanb481af72020-07-09 15:22:00 +100060 base::Closure disconnect_handler,
Michael Martisa74af932018-08-13 16:52:36 +100061 const std::string& model_dir)
Honglin Yuf33dce32019-12-05 15:10:39 +110062 : icu_data_(nullptr),
63 text_classifier_model_filename_(kTextClassifierModelFile),
64 builtin_model_metadata_(GetBuiltinModelMetadata()),
Michael Martisa74af932018-08-13 16:52:36 +100065 model_dir_(model_dir),
Andrew Moylanb481af72020-07-09 15:22:00 +100066 receiver_(this,
67 mojo::InterfaceRequest<
68 chromeos::machine_learning::mojom::MachineLearningService>(
69 std::move(pipe))) {
70 receiver_.set_disconnect_handler(std::move(disconnect_handler));
Andrew Moylanff6be512018-07-03 11:05:01 +100071}
72
Michael Martisa74af932018-08-13 16:52:36 +100073MachineLearningServiceImpl::MachineLearningServiceImpl(
Charles Zhaod4fb7b62020-08-25 17:21:58 +100074 mojo::ScopedMessagePipeHandle pipe,
75 base::Closure disconnect_handler,
76 dbus::Bus* bus)
Andrew Moylanb481af72020-07-09 15:22:00 +100077 : MachineLearningServiceImpl(
Charles Zhaod4fb7b62020-08-25 17:21:58 +100078 std::move(pipe), std::move(disconnect_handler), kSystemModelDir) {
79 if (bus) {
80 dlcservice_client_ = std::make_unique<DlcserviceClient>(bus);
81 }
82}
Michael Martisa74af932018-08-13 16:52:36 +100083
Honglin Yuf33dce32019-12-05 15:10:39 +110084void MachineLearningServiceImpl::SetTextClassifierModelFilenameForTesting(
85 const std::string& filename) {
86 text_classifier_model_filename_ = filename;
87}
88
Andrew Moylanb481af72020-07-09 15:22:00 +100089void MachineLearningServiceImpl::Clone(
90 mojo::PendingReceiver<MachineLearningService> receiver) {
91 clone_receivers_.Add(this, std::move(receiver));
Andrew Moylan2fb80af2020-07-08 10:52:08 +100092}
93
Honglin Yu0ed72352019-08-27 17:42:01 +100094void MachineLearningServiceImpl::LoadBuiltinModel(
95 BuiltinModelSpecPtr spec,
Andrew Moylanb481af72020-07-09 15:22:00 +100096 mojo::PendingReceiver<Model> receiver,
Qijiang Fan5d381a02020-04-19 23:42:37 +090097 LoadBuiltinModelCallback callback) {
Honglin Yu0ed72352019-08-27 17:42:01 +100098 // Unsupported models do not have metadata entries.
99 const auto metadata_lookup = builtin_model_metadata_.find(spec->id);
100 if (metadata_lookup == builtin_model_metadata_.end()) {
Honglin Yua81145a2019-09-23 15:20:13 +1000101 LOG(WARNING) << "LoadBuiltinModel requested for unsupported model ID "
102 << spec->id << ".";
Qijiang Fan5d381a02020-04-19 23:42:37 +0900103 std::move(callback).Run(LoadModelResult::MODEL_SPEC_ERROR);
Honglin Yu0ed72352019-08-27 17:42:01 +1000104 RecordModelSpecificationErrorEvent();
105 return;
106 }
107
108 const BuiltinModelMetadata& metadata = metadata_lookup->second;
109
110 DCHECK(!metadata.metrics_model_name.empty());
111
charleszhao5a7050e2020-07-14 15:21:41 +1000112 RequestMetrics request_metrics(metadata.metrics_model_name,
113 kMetricsRequestName);
Honglin Yu0ed72352019-08-27 17:42:01 +1000114 request_metrics.StartRecordingPerformanceMetrics();
115
116 // Attempt to load model.
117 const std::string model_path = model_dir_ + metadata.model_file;
118 std::unique_ptr<tflite::FlatBufferModel> model =
119 tflite::FlatBufferModel::BuildFromFile(model_path.c_str());
120 if (model == nullptr) {
121 LOG(ERROR) << "Failed to load model file '" << model_path << "'.";
Qijiang Fan5d381a02020-04-19 23:42:37 +0900122 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
Honglin Yu0ed72352019-08-27 17:42:01 +1000123 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
124 return;
125 }
126
Honglin Yuc0cef102020-01-17 15:26:01 +1100127 ModelImpl::Create(metadata.required_inputs, metadata.required_outputs,
Andrew Moylanb481af72020-07-09 15:22:00 +1000128 std::move(model), std::move(receiver),
Honglin Yuc0cef102020-01-17 15:26:01 +1100129 metadata.metrics_model_name);
Honglin Yu0ed72352019-08-27 17:42:01 +1000130
Qijiang Fan5d381a02020-04-19 23:42:37 +0900131 std::move(callback).Run(LoadModelResult::OK);
Honglin Yu0ed72352019-08-27 17:42:01 +1000132
133 request_metrics.FinishRecordingPerformanceMetrics();
134 request_metrics.RecordRequestEvent(LoadModelResult::OK);
135}
136
137void MachineLearningServiceImpl::LoadFlatBufferModel(
138 FlatBufferModelSpecPtr spec,
Andrew Moylanb481af72020-07-09 15:22:00 +1000139 mojo::PendingReceiver<Model> receiver,
Qijiang Fan5d381a02020-04-19 23:42:37 +0900140 LoadFlatBufferModelCallback callback) {
Honglin Yu0ed72352019-08-27 17:42:01 +1000141 DCHECK(!spec->metrics_model_name.empty());
142
charleszhao5a7050e2020-07-14 15:21:41 +1000143 RequestMetrics request_metrics(spec->metrics_model_name, kMetricsRequestName);
Honglin Yu0ed72352019-08-27 17:42:01 +1000144 request_metrics.StartRecordingPerformanceMetrics();
145
Andrew Moylan79b34a42020-07-08 11:13:11 +1000146 // Take the ownership of the content of `model_string` because `ModelImpl` has
Honglin Yu0ed72352019-08-27 17:42:01 +1000147 // to hold the memory.
148 auto model_string_impl =
149 std::make_unique<std::string>(std::move(spec->model_string));
150
151 std::unique_ptr<tflite::FlatBufferModel> model =
152 tflite::FlatBufferModel::BuildFromBuffer(model_string_impl->c_str(),
153 model_string_impl->length());
154 if (model == nullptr) {
155 LOG(ERROR) << "Failed to load model string of metric name: "
156 << spec->metrics_model_name << "'.";
Qijiang Fan5d381a02020-04-19 23:42:37 +0900157 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
Honglin Yu0ed72352019-08-27 17:42:01 +1000158 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
159 return;
160 }
161
Honglin Yuc0cef102020-01-17 15:26:01 +1100162 ModelImpl::Create(
Honglin Yu0ed72352019-08-27 17:42:01 +1000163 std::map<std::string, int>(spec->inputs.begin(), spec->inputs.end()),
164 std::map<std::string, int>(spec->outputs.begin(), spec->outputs.end()),
Andrew Moylanb481af72020-07-09 15:22:00 +1000165 std::move(model), std::move(model_string_impl), std::move(receiver),
Honglin Yu0ed72352019-08-27 17:42:01 +1000166 spec->metrics_model_name);
167
Qijiang Fan5d381a02020-04-19 23:42:37 +0900168 std::move(callback).Run(LoadModelResult::OK);
Honglin Yu0ed72352019-08-27 17:42:01 +1000169
170 request_metrics.FinishRecordingPerformanceMetrics();
171 request_metrics.RecordRequestEvent(LoadModelResult::OK);
172}
173
Honglin Yuf33dce32019-12-05 15:10:39 +1100174void MachineLearningServiceImpl::LoadTextClassifier(
Andrew Moylanb481af72020-07-09 15:22:00 +1000175 mojo::PendingReceiver<TextClassifier> receiver,
Honglin Yuf33dce32019-12-05 15:10:39 +1100176 LoadTextClassifierCallback callback) {
charleszhao5a7050e2020-07-14 15:21:41 +1000177 RequestMetrics request_metrics("TextClassifier", kMetricsRequestName);
Honglin Yuf33dce32019-12-05 15:10:39 +1100178 request_metrics.StartRecordingPerformanceMetrics();
179
180 // Attempt to load model.
181 std::string model_path = model_dir_ + text_classifier_model_filename_;
182 auto scoped_mmap =
183 std::make_unique<libtextclassifier3::ScopedMmap>(model_path);
184 if (!scoped_mmap->handle().ok()) {
185 LOG(ERROR) << "Failed to load the text classifier model file '"
186 << model_path << "'.";
187 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
188 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
189 return;
190 }
191
192 // Create the TextClassifier.
Honglin Yuc5100022020-07-09 11:54:27 +1000193 if (!TextClassifierImpl::Create(&scoped_mmap,
194 model_dir_ + kLanguageIdentificationModelFile,
Andrew Moylanb481af72020-07-09 15:22:00 +1000195 std::move(receiver))) {
Honglin Yuf33dce32019-12-05 15:10:39 +1100196 LOG(ERROR) << "Failed to create TextClassifierImpl object.";
197 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
198 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
199 return;
200 }
201
202 // initialize the icu library.
203 InitIcuIfNeeded();
204
205 std::move(callback).Run(LoadModelResult::OK);
206
207 request_metrics.FinishRecordingPerformanceMetrics();
208 request_metrics.RecordRequestEvent(LoadModelResult::OK);
209}
210
Charles Zhao6d467e62020-08-31 10:02:03 +1000211void LoadHandwritingModelFromDir(
212 HandwritingRecognizerSpecPtr spec,
213 mojo::PendingReceiver<HandwritingRecognizer> receiver,
214 MachineLearningServiceImpl::LoadHandwritingModelCallback callback,
215 const std::string& root_path) {
216 RequestMetrics request_metrics("HandwritingModel", kMetricsRequestName);
217 request_metrics.StartRecordingPerformanceMetrics();
218
219 // Returns error if root_path is empty.
220 if (root_path.empty()) {
221 std::move(callback).Run(LoadHandwritingModelResult::DLC_GET_PATH_ERROR);
222 request_metrics.RecordRequestEvent(
223 LoadHandwritingModelResult::DLC_GET_PATH_ERROR);
224 return;
225 }
226
227 // Load HandwritingLibrary.
228 auto* const hwr_library = ml::HandwritingLibrary::GetInstance(root_path);
229
230 if (hwr_library->GetStatus() != ml::HandwritingLibrary::Status::kOk) {
231 LOG(ERROR) << "Initialize ml::HandwritingLibrary with error "
232 << static_cast<int>(hwr_library->GetStatus());
233
234 switch (hwr_library->GetStatus()) {
235 case ml::HandwritingLibrary::Status::kLoadLibraryFailed: {
236 std::move(callback).Run(
237 LoadHandwritingModelResult::LOAD_NATIVE_LIB_ERROR);
238 request_metrics.RecordRequestEvent(
239 LoadHandwritingModelResult::LOAD_NATIVE_LIB_ERROR);
240 return;
241 }
242 case ml::HandwritingLibrary::Status::kFunctionLookupFailed: {
243 std::move(callback).Run(
244 LoadHandwritingModelResult::LOAD_FUNC_PTR_ERROR);
245 request_metrics.RecordRequestEvent(
246 LoadHandwritingModelResult::LOAD_FUNC_PTR_ERROR);
247 return;
248 }
249 default: {
250 std::move(callback).Run(LoadHandwritingModelResult::LOAD_MODEL_ERROR);
251 request_metrics.RecordRequestEvent(
252 LoadHandwritingModelResult::LOAD_MODEL_ERROR);
253 return;
254 }
255 }
256 }
257
258 // Create HandwritingRecognizer.
259 if (!HandwritingRecognizerImpl::Create(std::move(spec),
260 std::move(receiver))) {
261 LOG(ERROR) << "LoadHandwritingRecognizer returned false.";
262 std::move(callback).Run(LoadHandwritingModelResult::LOAD_MODEL_FILES_ERROR);
263 request_metrics.RecordRequestEvent(
264 LoadHandwritingModelResult::LOAD_MODEL_FILES_ERROR);
265 return;
266 }
267
268 std::move(callback).Run(LoadHandwritingModelResult::OK);
269 request_metrics.FinishRecordingPerformanceMetrics();
270 request_metrics.RecordRequestEvent(LoadHandwritingModelResult::OK);
271}
272
273void MachineLearningServiceImpl::LoadHandwritingModel(
274 chromeos::machine_learning::mojom::HandwritingRecognizerSpecPtr spec,
275 mojo::PendingReceiver<
276 chromeos::machine_learning::mojom::HandwritingRecognizer> receiver,
277 LoadHandwritingModelCallback callback) {
278 // If handwriting is installed on rootfs, load it from there.
279 if (ml::HandwritingLibrary::IsUseLibHandwritingEnabled()) {
280 LoadHandwritingModelFromDir(
281 std::move(spec), std::move(receiver), std::move(callback),
282 ml::HandwritingLibrary::kHandwritingDefaultModelDir);
283 return;
284 }
285
286 // If handwriting is installed as DLC, get the dir and subsequently load it
287 // from there.
288 if (ml::HandwritingLibrary::IsUseLibHandwritingDlcEnabled()) {
289 dlcservice_client_->GetDlcRootPath(
290 "libhandwriting",
291 base::BindOnce(&LoadHandwritingModelFromDir, std::move(spec),
292 std::move(receiver), std::move(callback)));
293 return;
294 }
295
296 // If handwriting is not on rootfs and not in DLC, this function should not
297 // be called.
298 LOG(ERROR) << "Calling LoadHandwritingModel without Handwriting enabled "
299 "should never happen.";
300 std::move(callback).Run(LoadHandwritingModelResult::LOAD_MODEL_ERROR);
charleszhao05c5a4a2020-06-09 16:49:54 +1000301}
302
303void MachineLearningServiceImpl::LoadHandwritingModelWithSpec(
304 HandwritingRecognizerSpecPtr spec,
Andrew Moylanb481af72020-07-09 15:22:00 +1000305 mojo::PendingReceiver<HandwritingRecognizer> receiver,
Charles Zhaoc882eb02020-07-27 10:02:35 +1000306 LoadHandwritingModelWithSpecCallback callback) {
charleszhao5a7050e2020-07-14 15:21:41 +1000307 RequestMetrics request_metrics("HandwritingModel", kMetricsRequestName);
charleszhao17777f92020-04-23 12:53:11 +1000308 request_metrics.StartRecordingPerformanceMetrics();
309
310 // Load HandwritingLibrary.
311 auto* const hwr_library = ml::HandwritingLibrary::GetInstance();
312
313 if (hwr_library->GetStatus() ==
314 ml::HandwritingLibrary::Status::kNotSupported) {
315 LOG(ERROR) << "Initialize ml::HandwritingLibrary with error "
316 << static_cast<int>(hwr_library->GetStatus());
317
318 std::move(callback).Run(LoadModelResult::FEATURE_NOT_SUPPORTED_ERROR);
319 request_metrics.RecordRequestEvent(
320 LoadModelResult::FEATURE_NOT_SUPPORTED_ERROR);
321 return;
322 }
323
324 if (hwr_library->GetStatus() != ml::HandwritingLibrary::Status::kOk) {
325 LOG(ERROR) << "Initialize ml::HandwritingLibrary with error "
326 << static_cast<int>(hwr_library->GetStatus());
327
328 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
329 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
330 return;
331 }
332
333 // Create HandwritingRecognizer.
Andrew Moylanb481af72020-07-09 15:22:00 +1000334 if (!HandwritingRecognizerImpl::Create(std::move(spec),
335 std::move(receiver))) {
charleszhao17777f92020-04-23 12:53:11 +1000336 LOG(ERROR) << "LoadHandwritingRecognizer returned false.";
337 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
338 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
339 return;
340 }
341
342 std::move(callback).Run(LoadModelResult::OK);
343 request_metrics.FinishRecordingPerformanceMetrics();
344 request_metrics.RecordRequestEvent(LoadModelResult::OK);
345}
346
Honglin Yuf33dce32019-12-05 15:10:39 +1100347void MachineLearningServiceImpl::InitIcuIfNeeded() {
348 if (icu_data_ == nullptr) {
349 // Need to load the data file again.
350 int64_t file_size;
351 const base::FilePath icu_data_file_path(kIcuDataFilePath);
352 CHECK(base::GetFileSize(icu_data_file_path, &file_size));
353 icu_data_ = new char[file_size];
354 CHECK(base::ReadFile(icu_data_file_path, icu_data_,
355 static_cast<int>(file_size)) == file_size);
356 // Init the Icu library.
357 UErrorCode err = U_ZERO_ERROR;
358 udata_setCommonData(reinterpret_cast<void*>(icu_data_), &err);
359 DCHECK(err == U_ZERO_ERROR);
360 // Never try to load Icu data from files.
361 udata_setFileAccess(UDATA_ONLY_PACKAGES, &err);
362 }
363}
364
Andrew Moylanff6be512018-07-03 11:05:01 +1000365} // namespace ml