1 /*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "nncompiled_cache.h"
17
18 #include <unistd.h>
19 #include <functional>
20 #include <memory>
21 #include <limits>
22 #include <cstdio>
23
24 #include "utils.h"
25 #include "backend_manager.h"
26 #include "nnbackend.h"
27
28 namespace OHOS {
29 namespace NeuralNetworkRuntime {
30 constexpr int32_t NULL_PTR_LENGTH = 0;
31 constexpr int32_t NUMBER_CACHE_INFO_MEMBERS = 3;
32 constexpr int32_t NUMBER_CACHE_INFO_EXTENSION_MEMBERS = 2;
33 constexpr int32_t HEX_UNIT = 16;
34 constexpr char ROOT_DIR_STR = '/';
35 constexpr char DOUBLE_SLASH_STR[] = "//";
36 constexpr int OPVERSION_SUBSTR_NUM = 2;
37 const std::string CURRENT_VERSION = "0x00000000";
38 const std::string HIAI_VERSION_PATH = "/data/data/hiai/version";
39
Save(const std::vector<OHOS::NeuralNetworkRuntime::Buffer> & caches,const std::string & cacheDir,uint32_t version)40 OH_NN_ReturnCode NNCompiledCache::Save(const std::vector<OHOS::NeuralNetworkRuntime::Buffer>& caches,
41 const std::string& cacheDir,
42 uint32_t version)
43 {
44 LOGI("[NNCompiledCache::Save] m_isExceedRamLimit: %{public}d", static_cast<int>(m_isExceedRamLimit));
45 if (caches.empty()) {
46 LOGE("[NNCompiledCache] Save failed, caches is empty.");
47 return OH_NN_INVALID_PARAMETER;
48 }
49
50 if (m_device == nullptr) {
51 LOGE("[NNCompiledCache] Save failed, m_device is empty.");
52 return OH_NN_INVALID_PARAMETER;
53 }
54
55 OH_NN_ReturnCode ret = GenerateCacheFiles(caches, cacheDir, version);
56 if (ret != OH_NN_SUCCESS) {
57 LOGE("[NNCompiledCache] Save failed, error happened when calling GenerateCacheFiles.");
58 return ret;
59 }
60
61 LOGI("[NNCompiledCache] Save success. %zu caches are saved.", caches.size());
62 return OH_NN_SUCCESS;
63 }
64
Restore(const std::string & cacheDir,uint32_t version,std::vector<OHOS::NeuralNetworkRuntime::Buffer> & caches)65 OH_NN_ReturnCode NNCompiledCache::Restore(const std::string& cacheDir,
66 uint32_t version,
67 std::vector<OHOS::NeuralNetworkRuntime::Buffer>& caches)
68 {
69 if (cacheDir.empty()) {
70 LOGE("[NNCompiledCache] Restore failed, cacheDir is empty.");
71 return OH_NN_INVALID_PARAMETER;
72 }
73
74 if (!caches.empty()) {
75 LOGE("[NNCompiledCache] Restore failed, caches is not empty.");
76 return OH_NN_INVALID_PARAMETER;
77 }
78
79 if (m_device == nullptr) {
80 LOGE("[NNCompiledCache] Restore failed, m_device is empty.");
81 return OH_NN_INVALID_PARAMETER;
82 }
83
84 std::string cacheInfoPath = cacheDir + "/" + m_modelName + "cache_info.nncache";
85 char path[PATH_MAX];
86 if (realpath(cacheInfoPath.c_str(), path) == nullptr) {
87 LOGE("[NNCompiledCache] Restore failed, fail to get the real path of cacheInfoPath.");
88 return OH_NN_INVALID_PARAMETER;
89 }
90 if (access(path, F_OK) != 0) {
91 LOGE("[NNCompiledCache] Restore failed, cacheInfoPath is not exist.");
92 return OH_NN_INVALID_PARAMETER;
93 }
94
95 NNCompiledCacheInfo cacheInfo;
96 OH_NN_ReturnCode ret = CheckCacheInfo(cacheInfo, path);
97 if (ret != OH_NN_SUCCESS) {
98 LOGE("[NNCompiledCache] Restore failed, error happened when calling CheckCacheInfo.");
99 return ret;
100 }
101
102 if (static_cast<int64_t>(version) > cacheInfo.version) {
103 LOGE("[NNCompiledCache] Restore failed, version is not match.");
104 return OH_NN_INVALID_PARAMETER;
105 }
106
107 if (static_cast<int64_t>(version) < cacheInfo.version) {
108 LOGE("[NNCompiledCache] Restore failed, the current version is lower than the cache files, "
109 "please set a higher version.");
110 return OH_NN_OPERATION_FORBIDDEN;
111 }
112
113 for (uint32_t i = 0; i < cacheInfo.fileNumber; ++i) {
114 std::string cacheModelPath = cacheDir + "/" + m_modelName + std::to_string(i) + ".nncache";
115 OHOS::NeuralNetworkRuntime::Buffer modelBuffer;
116 ret = ReadCacheModelFile(cacheModelPath, modelBuffer);
117 if (ret != OH_NN_SUCCESS) {
118 LOGE("[NNCompiledCache] Restore failed, error happened when calling ReadCacheModelFile.");
119 return ret;
120 }
121
122 if (GetCrc16(static_cast<char*>(modelBuffer.data), modelBuffer.length) !=
123 cacheInfo.modelCheckSum[i]) {
124 LOGE("[NNCompiledCache] Restore failed, the cache model file %{public}s has been changed.",
125 cacheModelPath.c_str());
126 return OH_NN_INVALID_FILE;
127 }
128
129 caches.emplace_back(std::move(modelBuffer));
130 }
131
132 return ret;
133 }
134
SetBackend(size_t backendID)135 OH_NN_ReturnCode NNCompiledCache::SetBackend(size_t backendID)
136 {
137 BackendManager& backendManager = BackendManager::GetInstance();
138 std::shared_ptr<Backend> backend = backendManager.GetBackend(backendID);
139 if (backend == nullptr) {
140 LOGE("[NNCompiledCache] SetBackend failed, backend with backendID %{public}zu is not exist.", backendID);
141 return OH_NN_INVALID_PARAMETER;
142 }
143
144 std::shared_ptr<NNBackend> nnBackend = std::reinterpret_pointer_cast<NNBackend>(backend);
145 m_device = nnBackend->GetDevice();
146 if (m_device == nullptr) {
147 LOGE("[NNCompiledCache] SetBackend failed, device with backendID %{public}zu is not exist.", backendID);
148 return OH_NN_FAILED;
149 }
150
151 m_backendID = backendID;
152 return OH_NN_SUCCESS;
153 }
154
SetModelName(const std::string & modelName)155 void NNCompiledCache::SetModelName(const std::string& modelName)
156 {
157 m_modelName = modelName;
158 }
159
SetIsExceedRamLimit(const bool isExceedRamLimit)160 void NNCompiledCache::SetIsExceedRamLimit(const bool isExceedRamLimit)
161 {
162 m_isExceedRamLimit = isExceedRamLimit;
163 }
164
GenerateCacheFiles(const std::vector<OHOS::NeuralNetworkRuntime::Buffer> & caches,const std::string & cacheDir,uint32_t version) const165 OH_NN_ReturnCode NNCompiledCache::GenerateCacheFiles(const std::vector<OHOS::NeuralNetworkRuntime::Buffer>& caches,
166 const std::string& cacheDir,
167 uint32_t version) const
168 {
169 const size_t cacheNumber = caches.size();
170 uint32_t cacheSize = NUMBER_CACHE_INFO_MEMBERS + cacheNumber + NUMBER_CACHE_INFO_EXTENSION_MEMBERS;
171 std::unique_ptr<int64_t[]> cacheInfo = CreateUniquePtr<int64_t[]>(cacheSize);
172 if (cacheInfo == nullptr) {
173 LOGE("[NNCompiledCache] GenerateCacheFiles failed, fail to create cacheInfo instance.");
174 return OH_NN_MEMORY_ERROR;
175 }
176
177 OH_NN_ReturnCode ret = GenerateCacheModel(caches, cacheInfo, cacheDir, version);
178 if (ret != OH_NN_SUCCESS) {
179 LOGE("[NNCompiledCache] GenerateCacheFiles failed, error happened when calling GenerateCacheModel.");
180 return ret;
181 }
182
183 uint32_t infoCharNumber = cacheSize * sizeof(uint64_t);
184 ret = WriteCacheInfo(infoCharNumber, cacheInfo, cacheDir);
185 if (ret != OH_NN_SUCCESS) {
186 LOGE("[NNCompiledCache] GenerateCacheFiles failed, error happened when calling WriteCacheInfo.");
187 return ret;
188 }
189
190 return OH_NN_SUCCESS;
191 }
192
GenerateCacheModel(const std::vector<OHOS::NeuralNetworkRuntime::Buffer> & caches,std::unique_ptr<int64_t[]> & cacheInfo,const std::string & cacheDir,uint32_t version) const193 OH_NN_ReturnCode NNCompiledCache::GenerateCacheModel(const std::vector<OHOS::NeuralNetworkRuntime::Buffer>& caches,
194 std::unique_ptr<int64_t[]>& cacheInfo,
195 const std::string& cacheDir,
196 uint32_t version) const
197 {
198 size_t cacheNumber = caches.size();
199 if (cacheNumber == 0 || cacheNumber > NN_CACHE_FILE_NUMBER_MAX) {
200 LOGE("[NNCompiledCache] Caches size is equal 0 or greater than 100.");
201 return OH_NN_FAILED;
202 }
203
204 auto cacheInfoPtr = cacheInfo.get();
205 *cacheInfoPtr++ = static_cast<int64_t>(cacheNumber);
206 *cacheInfoPtr++ = static_cast<int64_t>(version);
207 *cacheInfoPtr++ = static_cast<int64_t>(m_backendID); // Should call SetBackend first.
208
209 // standardize the input dir
210 OH_NN_ReturnCode ret = OH_NN_SUCCESS;
211 char path[PATH_MAX];
212 if (realpath(cacheDir.c_str(), path) == nullptr) {
213 LOGE("[NNCompiledCache] GenerateCacheModel failed, fail to get the real path of cacheDir.");
214 return OH_NN_INVALID_PARAMETER;
215 }
216
217 // verify the Standardized path available
218 ret = VerifyCachePath(path);
219 if (ret != OH_NN_SUCCESS) {
220 LOGE("[NNCompiledCache] GenerateCacheModel failed, fail to verify the file path of cacheDir.");
221 return ret;
222 }
223
224 std::string cachePath = path;
225 for (size_t i = 0; i < cacheNumber; ++i) {
226 std::string cacheModelFile = cachePath + "/" + m_modelName + std::to_string(i) + ".nncache";
227 std::ofstream cacheModelStream(cacheModelFile, std::ios::binary | std::ios::out | std::ios::trunc);
228 if (cacheModelStream.fail()) {
229 LOGE("[NNCompiledCache] GenerateCacheModel failed, model cache file is invalid.");
230 return OH_NN_INVALID_PARAMETER;
231 }
232
233 uint64_t checkSum =
234 static_cast<int64_t>(GetCrc16(static_cast<char*>(caches[i].data), caches[i].length));
235 *cacheInfoPtr++ = checkSum;
236 if (!cacheModelStream.write(static_cast<const char*>(caches[i].data), caches[i].length)) {
237 LOGE("[NNCompiledCache] GenerateCacheModel failed, fail to write cache model.");
238 cacheModelStream.close();
239 return OH_NN_SAVE_CACHE_EXCEPTION;
240 };
241
242 cacheModelStream.close();
243 }
244
245 std::string currentVersion = CURRENT_VERSION;
246 char versionPath[PATH_MAX];
247 if (realpath(HIAI_VERSION_PATH.c_str(), versionPath) != nullptr) {
248 std::ifstream inf(versionPath);
249 if (inf.is_open()) {
250 getline(inf, currentVersion);
251 }
252 inf.close();
253 }
254
255 int currentOpVersion = std::stoi(currentVersion.substr(OPVERSION_SUBSTR_NUM));
256 *cacheInfoPtr++ = currentOpVersion;
257
258 LOGI("[NNCompiledCache::GenerateCacheModel] m_isExceedRamLimit: %{public}d", static_cast<int>(m_isExceedRamLimit));
259 if (m_isExceedRamLimit) {
260 *cacheInfoPtr++ = 1;
261 } else {
262 *cacheInfoPtr++ = 0;
263 }
264
265 return OH_NN_SUCCESS;
266 }
267
WriteCacheInfo(uint32_t cacheSize,std::unique_ptr<int64_t[]> & cacheInfo,const std::string & cacheDir) const268 OH_NN_ReturnCode NNCompiledCache::WriteCacheInfo(uint32_t cacheSize,
269 std::unique_ptr<int64_t[]>& cacheInfo,
270 const std::string& cacheDir) const
271 {
272 // standardize the input dir
273 char path[PATH_MAX];
274 if (realpath(cacheDir.c_str(), path) == nullptr) {
275 LOGE("[NNCompiledCache] WriteCacheInfo failed, fail to get the real path of cacheDir.");
276 return OH_NN_INVALID_PARAMETER;
277 }
278
279 // verify the Standardized path available
280 OH_NN_ReturnCode ret = VerifyCachePath(path);
281 if (ret != OH_NN_SUCCESS) {
282 LOGE("[NNCompiledCache] WriteCacheInfo failed, fail to verify the file path of cacheDir.");
283 return ret;
284 }
285
286 std::string cachePath = path;
287 std::string cacheInfoPath = cachePath + "/" + m_modelName + "cache_info.nncache";
288 std::ofstream cacheInfoStream(cacheInfoPath, std::ios::binary | std::ios::out | std::ios::trunc);
289 if (cacheInfoStream.fail()) {
290 LOGE("[NNCompiledCache] WriteCacheInfo failed, model cache info file is invalid.");
291 return OH_NN_INVALID_FILE;
292 }
293
294 if (!cacheInfoStream.write(reinterpret_cast<const char*>(cacheInfo.get()), cacheSize)) {
295 LOGE("[NNCompiledCache] WriteCacheInfo failed, fail to write cache info.");
296 cacheInfoStream.close();
297 return OH_NN_SAVE_CACHE_EXCEPTION;
298 }
299
300 cacheInfoStream.close();
301 return OH_NN_SUCCESS;
302 }
303
CheckCacheInfo(NNCompiledCacheInfo & modelCacheInfo,const std::string & cacheInfoPath) const304 OH_NN_ReturnCode NNCompiledCache::CheckCacheInfo(NNCompiledCacheInfo& modelCacheInfo,
305 const std::string& cacheInfoPath) const
306 {
307 // cacheInfoPath is validated outside.
308 std::ifstream infoCacheFile(cacheInfoPath.c_str(), std::ios::in | std::ios::binary);
309 if (!infoCacheFile) {
310 LOGE("[NNCompiledCache] CheckCacheInfo failed, error happened when opening cache info file.");
311 return OH_NN_INVALID_FILE;
312 }
313
314 int charNumber = NUMBER_CACHE_INFO_MEMBERS * sizeof(uint64_t);
315 if (!infoCacheFile.read(reinterpret_cast<char*>(&(modelCacheInfo)), charNumber)) {
316 LOGE("[NNCompiledCache] CheckCacheInfo failed, error happened when reading cache info file.");
317 infoCacheFile.close();
318 return OH_NN_INVALID_FILE;
319 }
320
321 // modelCacheInfo.deviceId type is int64_t,
322 // it is transformed from size_t value, so the transform here will not truncate value.
323 size_t deviceId = static_cast<size_t>(modelCacheInfo.deviceId);
324 if (deviceId != m_backendID) {
325 LOGE("[NNCompiledCache] CheckCacheInfo failed. The deviceId in the cache files "
326 "is different from current deviceId,"
327 "please change the cache directory or current deviceId.");
328 infoCacheFile.close();
329 return OH_NN_INVALID_PARAMETER;
330 }
331
332 std::vector<int64_t> modelCheckSum;
333 modelCheckSum.resize(modelCacheInfo.fileNumber);
334 modelCacheInfo.modelCheckSum.resize(modelCacheInfo.fileNumber);
335 if (!infoCacheFile.read(reinterpret_cast<char*>(&modelCheckSum[0]),
336 modelCacheInfo.fileNumber * sizeof(uint64_t))) {
337 LOGE("[NNCompiledCache] CheckCacheInfo failed. The info cache file has been changed.");
338 infoCacheFile.close();
339 return OH_NN_INVALID_FILE;
340 }
341
342 for (uint32_t i = 0; i < modelCacheInfo.fileNumber; ++i) {
343 modelCacheInfo.modelCheckSum[i] = static_cast<unsigned short>(modelCheckSum[i]);
344 }
345
346 if (!infoCacheFile.read(reinterpret_cast<char*>(&(modelCacheInfo.opVersion)), sizeof(uint64_t))) {
347 LOGW("[NNCompiledCache] opVersion failed.");
348 }
349
350 if (!infoCacheFile.read(reinterpret_cast<char*>(&(modelCacheInfo.isExceedRamLimit)), sizeof(uint64_t))) {
351 LOGW("[NNCompiledCache] isExceedRamLimit failed.");
352 }
353
354 infoCacheFile.close();
355 return OH_NN_SUCCESS;
356 }
357
ReadCacheModelFile(const std::string & filePath,OHOS::NeuralNetworkRuntime::Buffer & cache) const358 OH_NN_ReturnCode NNCompiledCache::ReadCacheModelFile(const std::string& filePath,
359 OHOS::NeuralNetworkRuntime::Buffer& cache) const
360 {
361 char path[PATH_MAX];
362 if (realpath(filePath.c_str(), path) == nullptr) {
363 LOGE("[NNCompiledCache] ReadCacheModelFile failed, fail to get the real path of filePath.");
364 return OH_NN_INVALID_PARAMETER;
365 }
366 if (access(path, 0) != 0) {
367 LOGE("[NNCompiledCache] ReadCacheModelFile failed, %{public}s is not exist.", path);
368 return OH_NN_INVALID_PARAMETER;
369 }
370
371 FILE* pFile = fopen(path, "rb");
372 if (pFile == NULL) {
373 LOGE("[NNCompiledCache] ReadCacheModelFile failed, file fopen failed.");
374 return OH_NN_INVALID_FILE;
375 }
376
377 long fsize{-1};
378 OH_NN_ReturnCode ret = GetCacheFileLength(pFile, fsize);
379 if (ret != OH_NN_SUCCESS) {
380 fclose(pFile);
381 LOGE("[NNCompiledCache] ReadCacheModelFile failed, get file %{public}s length fialed.", filePath.c_str());
382 return ret;
383 }
384
385 rewind(pFile);
386
387 char* ptr = static_cast<char*>(m_device->AllocateBuffer(fsize));
388 if (ptr == nullptr) {
389 LOGE("[NNCompiledCache] ReadCacheModelFile failed, failed to allocate memory.");
390 fclose(pFile);
391 return OH_NN_MEMORY_ERROR;
392 }
393
394 LOGI("ReadCacheModelFile read start.");
395 size_t result = fread(ptr, 1, fsize, pFile); // size of each object in bytes is 1
396 LOGI("ReadCacheModelFile read end.");
397 if (result != static_cast<size_t>(fsize)) {
398 LOGE("[NNCompiledCache] ReadCacheModelFile failed, failed to read file.");
399 fclose(pFile);
400 m_device->ReleaseBuffer(ptr);
401 ptr = nullptr;
402 return OH_NN_INVALID_FILE;
403 }
404
405 fclose(pFile);
406 cache.data = ptr;
407 cache.length = static_cast<size_t>(fsize); // fsize should be non-negative, safe to cast.
408 return OH_NN_SUCCESS;
409 }
410
GetCrc16(char * buffer,size_t length) const411 unsigned short NNCompiledCache::GetCrc16(char* buffer, size_t length) const
412 {
413 unsigned int sum = 0;
414 while (length > 1) {
415 sum += *(reinterpret_cast<unsigned short*>(buffer));
416 length -= sizeof(unsigned short);
417 buffer += sizeof(unsigned short);
418 }
419
420 if (length > 0) {
421 sum += *(reinterpret_cast<unsigned char*>(buffer));
422 }
423
424 while (sum >> HEX_UNIT) {
425 sum = (sum >> HEX_UNIT) + (sum & 0xffff);
426 }
427
428 return static_cast<unsigned short>(~sum);
429 }
430
GetCacheFileLength(FILE * pFile,long & fileSize) const431 OH_NN_ReturnCode NNCompiledCache::GetCacheFileLength(FILE* pFile, long& fileSize) const
432 {
433 int ret = fseek(pFile, 0L, SEEK_END);
434 if (ret != 0) {
435 LOGE("[NNCompiledCache] GetCacheFileLength failed, fail to set the position of the next character "
436 "to be extracted from the input stream.");
437 return OH_NN_FAILED;
438 }
439
440 long handleValue = ftell(pFile);
441 if (handleValue == -1) {
442 LOGE("[NNCompiledCache] GetCacheFileLength failed, fail to get position of the input stream.");
443 return OH_NN_INVALID_FILE;
444 }
445
446 if (handleValue == NULL_PTR_LENGTH) {
447 LOGE("[NNCompiledCache] GetCacheFileLength failed, unable to read huge or empty input stream, "
448 "get cache file size=%{public}ld",
449 handleValue);
450 return OH_NN_INVALID_FILE;
451 }
452
453 fileSize = handleValue;
454 return OH_NN_SUCCESS;
455 }
456
VerifyCachePath(const std::string & cachePath) const457 OH_NN_ReturnCode NNCompiledCache::VerifyCachePath(const std::string& cachePath) const
458 {
459 // exception: input path is not start with '/'.
460 if (cachePath.find(ROOT_DIR_STR) != size_t(0)) {
461 LOGE("[NNCompiledCache] VerifyCachePath failed, input file dir=%{public}s is invalid, "
462 "should start with '/'.",
463 cachePath.c_str());
464 return OH_NN_INVALID_FILE;
465 }
466
467 // exception: input path contains continuous double '/'.
468 if (cachePath.find(DOUBLE_SLASH_STR) != std::string::npos) {
469 LOGE("[NNCompiledCache] VerifyCachePath failed, input file dir=%{public}s is invalid, "
470 "containing double '/'.",
471 cachePath.c_str());
472 return OH_NN_INVALID_FILE;
473 }
474
475 return OH_NN_SUCCESS;
476 }
477 } // namespace NeuralNetworkRuntime
478 } // namespace OHOS
479