blob: 44e4b4d4a3234a17ccfaa07dee65b8ff501317d6 [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"
charleszhao05c5a4a2020-06-09 16:49:54 +100021#include "ml/handwriting_path.h"
charleszhao17777f92020-04-23 12:53:11 +100022#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 Yuf33dce32019-12-05 15:10:39 +110026#include "ml/text_classifier_impl.h"
Michael Martisa74af932018-08-13 16:52:36 +100027
Andrew Moylanff6be512018-07-03 11:05:01 +100028namespace ml {
29
Michael Martisa74af932018-08-13 16:52:36 +100030namespace {
31
Honglin Yu0ed72352019-08-27 17:42:01 +100032using ::chromeos::machine_learning::mojom::BuiltinModelId;
33using ::chromeos::machine_learning::mojom::BuiltinModelSpecPtr;
34using ::chromeos::machine_learning::mojom::FlatBufferModelSpecPtr;
Andrew Moylanb481af72020-07-09 15:22:00 +100035using ::chromeos::machine_learning::mojom::HandwritingRecognizer;
charleszhao05c5a4a2020-06-09 16:49:54 +100036using ::chromeos::machine_learning::mojom::HandwritingRecognizerSpec;
37using ::chromeos::machine_learning::mojom::HandwritingRecognizerSpecPtr;
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(
Andrew Moylanb481af72020-07-09 15:22:00 +100074 mojo::ScopedMessagePipeHandle pipe, base::Closure disconnect_handler)
75 : MachineLearningServiceImpl(
76 std::move(pipe), std::move(disconnect_handler), kSystemModelDir) {}
Michael Martisa74af932018-08-13 16:52:36 +100077
Honglin Yuf33dce32019-12-05 15:10:39 +110078void MachineLearningServiceImpl::SetTextClassifierModelFilenameForTesting(
79 const std::string& filename) {
80 text_classifier_model_filename_ = filename;
81}
82
Andrew Moylanb481af72020-07-09 15:22:00 +100083void MachineLearningServiceImpl::Clone(
84 mojo::PendingReceiver<MachineLearningService> receiver) {
85 clone_receivers_.Add(this, std::move(receiver));
Andrew Moylan2fb80af2020-07-08 10:52:08 +100086}
87
Honglin Yu0ed72352019-08-27 17:42:01 +100088void MachineLearningServiceImpl::LoadBuiltinModel(
89 BuiltinModelSpecPtr spec,
Andrew Moylanb481af72020-07-09 15:22:00 +100090 mojo::PendingReceiver<Model> receiver,
Qijiang Fan5d381a02020-04-19 23:42:37 +090091 LoadBuiltinModelCallback callback) {
Honglin Yu0ed72352019-08-27 17:42:01 +100092 // Unsupported models do not have metadata entries.
93 const auto metadata_lookup = builtin_model_metadata_.find(spec->id);
94 if (metadata_lookup == builtin_model_metadata_.end()) {
Honglin Yua81145a2019-09-23 15:20:13 +100095 LOG(WARNING) << "LoadBuiltinModel requested for unsupported model ID "
96 << spec->id << ".";
Qijiang Fan5d381a02020-04-19 23:42:37 +090097 std::move(callback).Run(LoadModelResult::MODEL_SPEC_ERROR);
Honglin Yu0ed72352019-08-27 17:42:01 +100098 RecordModelSpecificationErrorEvent();
99 return;
100 }
101
102 const BuiltinModelMetadata& metadata = metadata_lookup->second;
103
104 DCHECK(!metadata.metrics_model_name.empty());
105
charleszhao5a7050e2020-07-14 15:21:41 +1000106 RequestMetrics request_metrics(metadata.metrics_model_name,
107 kMetricsRequestName);
Honglin Yu0ed72352019-08-27 17:42:01 +1000108 request_metrics.StartRecordingPerformanceMetrics();
109
110 // Attempt to load model.
111 const std::string model_path = model_dir_ + metadata.model_file;
112 std::unique_ptr<tflite::FlatBufferModel> model =
113 tflite::FlatBufferModel::BuildFromFile(model_path.c_str());
114 if (model == nullptr) {
115 LOG(ERROR) << "Failed to load model file '" << model_path << "'.";
Qijiang Fan5d381a02020-04-19 23:42:37 +0900116 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
Honglin Yu0ed72352019-08-27 17:42:01 +1000117 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
118 return;
119 }
120
Honglin Yuc0cef102020-01-17 15:26:01 +1100121 ModelImpl::Create(metadata.required_inputs, metadata.required_outputs,
Andrew Moylanb481af72020-07-09 15:22:00 +1000122 std::move(model), std::move(receiver),
Honglin Yuc0cef102020-01-17 15:26:01 +1100123 metadata.metrics_model_name);
Honglin Yu0ed72352019-08-27 17:42:01 +1000124
Qijiang Fan5d381a02020-04-19 23:42:37 +0900125 std::move(callback).Run(LoadModelResult::OK);
Honglin Yu0ed72352019-08-27 17:42:01 +1000126
127 request_metrics.FinishRecordingPerformanceMetrics();
128 request_metrics.RecordRequestEvent(LoadModelResult::OK);
129}
130
131void MachineLearningServiceImpl::LoadFlatBufferModel(
132 FlatBufferModelSpecPtr spec,
Andrew Moylanb481af72020-07-09 15:22:00 +1000133 mojo::PendingReceiver<Model> receiver,
Qijiang Fan5d381a02020-04-19 23:42:37 +0900134 LoadFlatBufferModelCallback callback) {
Honglin Yu0ed72352019-08-27 17:42:01 +1000135 DCHECK(!spec->metrics_model_name.empty());
136
charleszhao5a7050e2020-07-14 15:21:41 +1000137 RequestMetrics request_metrics(spec->metrics_model_name, kMetricsRequestName);
Honglin Yu0ed72352019-08-27 17:42:01 +1000138 request_metrics.StartRecordingPerformanceMetrics();
139
Andrew Moylan79b34a42020-07-08 11:13:11 +1000140 // Take the ownership of the content of `model_string` because `ModelImpl` has
Honglin Yu0ed72352019-08-27 17:42:01 +1000141 // to hold the memory.
142 auto model_string_impl =
143 std::make_unique<std::string>(std::move(spec->model_string));
144
145 std::unique_ptr<tflite::FlatBufferModel> model =
146 tflite::FlatBufferModel::BuildFromBuffer(model_string_impl->c_str(),
147 model_string_impl->length());
148 if (model == nullptr) {
149 LOG(ERROR) << "Failed to load model string of metric name: "
150 << spec->metrics_model_name << "'.";
Qijiang Fan5d381a02020-04-19 23:42:37 +0900151 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
Honglin Yu0ed72352019-08-27 17:42:01 +1000152 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
153 return;
154 }
155
Honglin Yuc0cef102020-01-17 15:26:01 +1100156 ModelImpl::Create(
Honglin Yu0ed72352019-08-27 17:42:01 +1000157 std::map<std::string, int>(spec->inputs.begin(), spec->inputs.end()),
158 std::map<std::string, int>(spec->outputs.begin(), spec->outputs.end()),
Andrew Moylanb481af72020-07-09 15:22:00 +1000159 std::move(model), std::move(model_string_impl), std::move(receiver),
Honglin Yu0ed72352019-08-27 17:42:01 +1000160 spec->metrics_model_name);
161
Qijiang Fan5d381a02020-04-19 23:42:37 +0900162 std::move(callback).Run(LoadModelResult::OK);
Honglin Yu0ed72352019-08-27 17:42:01 +1000163
164 request_metrics.FinishRecordingPerformanceMetrics();
165 request_metrics.RecordRequestEvent(LoadModelResult::OK);
166}
167
Honglin Yuf33dce32019-12-05 15:10:39 +1100168void MachineLearningServiceImpl::LoadTextClassifier(
Andrew Moylanb481af72020-07-09 15:22:00 +1000169 mojo::PendingReceiver<TextClassifier> receiver,
Honglin Yuf33dce32019-12-05 15:10:39 +1100170 LoadTextClassifierCallback callback) {
charleszhao5a7050e2020-07-14 15:21:41 +1000171 RequestMetrics request_metrics("TextClassifier", kMetricsRequestName);
Honglin Yuf33dce32019-12-05 15:10:39 +1100172 request_metrics.StartRecordingPerformanceMetrics();
173
174 // Attempt to load model.
175 std::string model_path = model_dir_ + text_classifier_model_filename_;
176 auto scoped_mmap =
177 std::make_unique<libtextclassifier3::ScopedMmap>(model_path);
178 if (!scoped_mmap->handle().ok()) {
179 LOG(ERROR) << "Failed to load the text classifier model file '"
180 << model_path << "'.";
181 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
182 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
183 return;
184 }
185
186 // Create the TextClassifier.
Honglin Yuc5100022020-07-09 11:54:27 +1000187 if (!TextClassifierImpl::Create(&scoped_mmap,
188 model_dir_ + kLanguageIdentificationModelFile,
Andrew Moylanb481af72020-07-09 15:22:00 +1000189 std::move(receiver))) {
Honglin Yuf33dce32019-12-05 15:10:39 +1100190 LOG(ERROR) << "Failed to create TextClassifierImpl object.";
191 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
192 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
193 return;
194 }
195
196 // initialize the icu library.
197 InitIcuIfNeeded();
198
199 std::move(callback).Run(LoadModelResult::OK);
200
201 request_metrics.FinishRecordingPerformanceMetrics();
202 request_metrics.RecordRequestEvent(LoadModelResult::OK);
203}
204
charleszhao17777f92020-04-23 12:53:11 +1000205void MachineLearningServiceImpl::LoadHandwritingModel(
Andrew Moylanb481af72020-07-09 15:22:00 +1000206 mojo::PendingReceiver<HandwritingRecognizer> receiver,
charleszhao17777f92020-04-23 12:53:11 +1000207 LoadHandwritingModelCallback callback) {
charleszhao05c5a4a2020-06-09 16:49:54 +1000208 // Use english as default language.
209 LoadHandwritingModelWithSpec(HandwritingRecognizerSpec::New("en"),
Andrew Moylanb481af72020-07-09 15:22:00 +1000210 std::move(receiver), std::move(callback));
charleszhao05c5a4a2020-06-09 16:49:54 +1000211}
212
213void MachineLearningServiceImpl::LoadHandwritingModelWithSpec(
214 HandwritingRecognizerSpecPtr spec,
Andrew Moylanb481af72020-07-09 15:22:00 +1000215 mojo::PendingReceiver<HandwritingRecognizer> receiver,
charleszhao05c5a4a2020-06-09 16:49:54 +1000216 LoadHandwritingModelCallback callback) {
charleszhao5a7050e2020-07-14 15:21:41 +1000217 RequestMetrics request_metrics("HandwritingModel", kMetricsRequestName);
charleszhao17777f92020-04-23 12:53:11 +1000218 request_metrics.StartRecordingPerformanceMetrics();
219
220 // Load HandwritingLibrary.
221 auto* const hwr_library = ml::HandwritingLibrary::GetInstance();
222
223 if (hwr_library->GetStatus() ==
224 ml::HandwritingLibrary::Status::kNotSupported) {
225 LOG(ERROR) << "Initialize ml::HandwritingLibrary with error "
226 << static_cast<int>(hwr_library->GetStatus());
227
228 std::move(callback).Run(LoadModelResult::FEATURE_NOT_SUPPORTED_ERROR);
229 request_metrics.RecordRequestEvent(
230 LoadModelResult::FEATURE_NOT_SUPPORTED_ERROR);
231 return;
232 }
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 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
239 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
240 return;
241 }
242
charleszhao05c5a4a2020-06-09 16:49:54 +1000243 if (!GetModelPaths(spec.Clone()).has_value()) {
244 LOG(ERROR) << "LoadHandwritingRecognizer is not called because language "
245 "code is not supported.";
246
247 std::move(callback).Run(LoadModelResult::LANGUAGE_NOT_SUPPORTED_ERROR);
248 request_metrics.RecordRequestEvent(
249 LoadModelResult::LANGUAGE_NOT_SUPPORTED_ERROR);
250 return;
251 }
252
charleszhao17777f92020-04-23 12:53:11 +1000253 // Create HandwritingRecognizer.
Andrew Moylanb481af72020-07-09 15:22:00 +1000254 if (!HandwritingRecognizerImpl::Create(std::move(spec),
255 std::move(receiver))) {
charleszhao17777f92020-04-23 12:53:11 +1000256 LOG(ERROR) << "LoadHandwritingRecognizer returned false.";
257 std::move(callback).Run(LoadModelResult::LOAD_MODEL_ERROR);
258 request_metrics.RecordRequestEvent(LoadModelResult::LOAD_MODEL_ERROR);
259 return;
260 }
261
262 std::move(callback).Run(LoadModelResult::OK);
263 request_metrics.FinishRecordingPerformanceMetrics();
264 request_metrics.RecordRequestEvent(LoadModelResult::OK);
265}
266
Honglin Yuf33dce32019-12-05 15:10:39 +1100267void MachineLearningServiceImpl::InitIcuIfNeeded() {
268 if (icu_data_ == nullptr) {
269 // Need to load the data file again.
270 int64_t file_size;
271 const base::FilePath icu_data_file_path(kIcuDataFilePath);
272 CHECK(base::GetFileSize(icu_data_file_path, &file_size));
273 icu_data_ = new char[file_size];
274 CHECK(base::ReadFile(icu_data_file_path, icu_data_,
275 static_cast<int>(file_size)) == file_size);
276 // Init the Icu library.
277 UErrorCode err = U_ZERO_ERROR;
278 udata_setCommonData(reinterpret_cast<void*>(icu_data_), &err);
279 DCHECK(err == U_ZERO_ERROR);
280 // Never try to load Icu data from files.
281 udata_setFileAccess(UDATA_ONLY_PACKAGES, &err);
282 }
283}
284
Andrew Moylanff6be512018-07-03 11:05:01 +1000285} // namespace ml