Lưu nội dung biên dịch vào bộ nhớ đệm

Từ Android 10, Neural Networks API (NNAPI) cung cấp các hàm để hỗ trợ việc lưu vào bộ nhớ đệm các cấu phần phần mềm biên dịch, giúp giảm thời gian dùng để biên dịch khi một ứng dụng khởi động. Khi sử dụng chức năng lưu vào bộ nhớ đệm này, trình điều khiển không cần quản lý hoặc dọn dẹp các tệp được lưu vào bộ nhớ đệm. Đây là một tính năng không bắt buộc mà bạn có thể triển khai bằng NN HAL 1.2. Để biết thêm thông tin về hàm này, hãy xem ANeuralNetworksCompilation_setCaching.

Trình điều khiển cũng có thể triển khai việc lưu vào bộ nhớ đệm quá trình biên dịch độc lập với NNAPI. Bạn có thể triển khai việc này bất kể có sử dụng các tính năng lưu vào bộ nhớ đệm của NNAPI NDK và HAL hay không. AOSP cung cấp một thư viện tiện ích cấp thấp (công cụ lưu vào bộ nhớ đệm). Để biết thêm thông tin, hãy xem Triển khai công cụ lưu vào bộ nhớ đệm.

Tổng quan về quy trình công việc

Phần này mô tả các quy trình công việc chung với tính năng lưu vào bộ nhớ đệm quá trình biên dịch được triển khai.

Thông tin được cung cấp về bộ nhớ đệm và kết quả tìm kiếm trong bộ nhớ cache

  1. Ứng dụng truyền một thư mục lưu vào bộ nhớ đệm và một tổng kiểm tra riêng cho mô hình.
  2. Môi trường thời gian chạy NNAPI tìm các tệp được lưu vào bộ nhớ đệm dựa trên tổng kiểm tra, tuỳ chọn thực thi và kết quả phân vùng rồi tìm các tệp đó.
  3. NNAPI mở các tệp được lưu vào bộ nhớ đệm và truyền các trình xử lý đến trình điều khiển bằng prepareModelFromCache.
  4. Trình điều khiển chuẩn bị mô hình trực tiếp từ các tệp được lưu vào bộ nhớ đệm và trả về mô hình đã chuẩn bị.

Thông tin được cung cấp về bộ nhớ đệm và thiếu bộ nhớ đệm

  1. Ứng dụng truyền một tổng kiểm tra riêng cho mô hình và một thư mục lưu vào bộ nhớ đệm.
  2. Môi trường thời gian chạy NNAPI tìm các tệp được lưu vào bộ nhớ đệm dựa trên tổng kiểm tra, tuỳ chọn thực thi và kết quả phân vùng nhưng không tìm thấy các tệp được lưu vào bộ nhớ đệm.
  3. NNAPI tạo các tệp được lưu vào bộ nhớ đệm trống dựa trên tổng kiểm tra, tuỳ chọn thực thi và phân vùng, mở các tệp được lưu vào bộ nhớ đệm và truyền các trình xử lý và mô hình đến trình điều khiển bằng prepareModel_1_2.
  4. Trình điều khiển biên dịch mô hình, ghi thông tin lưu vào bộ nhớ đệm vào các tệp được lưu vào bộ nhớ đệm và trả về mô hình đã chuẩn bị.

Không cung cấp thông tin về bộ nhớ đệm

  1. Ứng dụng gọi quá trình biên dịch mà không cung cấp thông tin nào về bộ nhớ đệm.
  2. Ứng dụng không truyền thông tin nào liên quan đến việc lưu vào bộ nhớ đệm.
  3. Môi trường thời gian chạy NNAPI truyền mô hình đến trình điều khiển bằng prepareModel_1_2.
  4. Trình điều khiển biên dịch mô hình và trả về mô hình đã chuẩn bị.

Thông tin về bộ nhớ đệm

Thông tin được cung cấp về bộ nhớ đệm cho trình điều khiển bao gồm mã thông báo và trình xử lý tệp được lưu vào bộ nhớ đệm.

Mã thông báo

Mã thông báo là mã thông báo lưu vào bộ nhớ đệm có độ dài Constant::BYTE_SIZE_OF_CACHE_TOKEN để xác định mô hình đã chuẩn bị. Bạn sẽ được cung cấp cùng một mã thông báo khi lưu các tệp được lưu vào bộ nhớ đệm bằng prepareModel_1_2 và truy xuất mô hình đã chuẩn bị bằng prepareModelFromCache. Ứng dụng của trình điều khiển nên chọn mã thông báo có tỷ lệ xung đột thấp. Trình điều khiển không thể phát hiện xung đột mã thông báo. Xung đột dẫn đến việc thực thi không thành công hoặc thực thi thành công nhưng tạo ra các giá trị đầu ra không chính xác.

Trình xử lý tệp được lưu vào bộ nhớ đệm (2 loại tệp được lưu vào bộ nhớ đệm)

Có 2 loại tệp được lưu vào bộ nhớ đệm là bộ nhớ đệm dữ liệubộ nhớ đệm mô hình.

  • Bộ nhớ đệm dữ liệu: Dùng để lưu vào bộ nhớ đệm dữ liệu hằng số, bao gồm cả bộ đệm tensor đã xử lý trước và chuyển đổi. Việc sửa đổi bộ nhớ đệm dữ liệu không được gây ra bất kỳ ảnh hưởng nào tệ hơn việc tạo ra các giá trị đầu ra không chính xác tại thời điểm thực thi.
  • Bộ nhớ đệm mô hình: Dùng để lưu vào bộ nhớ đệm dữ liệu nhạy cảm về bảo mật, chẳng hạn như mã máy thực thi đã biên dịch ở định dạng nhị phân gốc của thiết bị. Việc sửa đổi bộ nhớ đệm mô hình có thể ảnh hưởng đến hành vi thực thi của trình điều khiển và ứng dụng độc hại có thể tận dụng điều này để thực thi ngoài phạm vi quyền được cấp. Do đó, trình điều khiển phải kiểm tra xem bộ nhớ đệm mô hình có bị hỏng hay không trước khi chuẩn bị mô hình từ bộ nhớ đệm. Để biết thêm thông tin, hãy xem Bảo mật.

Trình điều khiển phải quyết định cách phân phối thông tin về bộ nhớ đệm giữa 2 loại tệp được lưu vào bộ nhớ đệm và báo cáo số lượng tệp được lưu vào bộ nhớ đệm cần thiết cho từng loại bằng getNumberOfCacheFilesNeeded.

Môi trường thời gian chạy NNAPI luôn mở các trình xử lý tệp được lưu vào bộ nhớ đệm với cả quyền đọc và ghi.

Bảo mật

Trong quá trình lưu vào bộ nhớ đệm quá trình biên dịch, bộ nhớ đệm mô hình có thể chứa dữ liệu nhạy cảm về bảo mật, chẳng hạn như mã máy thực thi đã biên dịch ở định dạng nhị phân gốc của thiết bị. Nếu không được bảo vệ đúng cách, việc sửa đổi bộ nhớ đệm mô hình có thể ảnh hưởng đến hành vi thực thi của trình điều khiển. Vì nội dung bộ nhớ đệm được lưu trữ trong thư mục ứng dụng, nên ứng dụng có thể sửa đổi các tệp được lưu vào bộ nhớ đệm. Ứng dụng bị lỗi có thể vô tình làm hỏng bộ nhớ đệm và ứng dụng độc hại có thể cố ý tận dụng điều này để thực thi mã chưa được xác minh trên thiết bị. Tuỳ thuộc vào đặc điểm của thiết bị, đây có thể là vấn đề về bảo mật. Do đó, trình điều khiển phải có khả năng phát hiện tình trạng hỏng bộ nhớ đệm mô hình tiềm ẩn trước khi chuẩn bị mô hình từ bộ nhớ đệm.

Một cách để thực hiện việc này là trình điều khiển duy trì một bản đồ từ mã thông báo đến hàm băm mật mã của bộ nhớ đệm mô hình. Trình điều khiển có thể lưu trữ mã thông báo và hàm băm của bộ nhớ đệm mô hình khi lưu quá trình biên dịch vào bộ nhớ đệm. Trình điều khiển kiểm tra hàm băm mới của bộ nhớ đệm mô hình với cặp mã thông báo và hàm băm đã ghi khi truy xuất quá trình biên dịch từ bộ nhớ đệm. Ánh xạ này phải được duy trì trong các lần khởi động lại hệ thống. Trình điều khiển có thể sử dụng dịch vụ khoá Android, thư viện tiện ích trongframework/ml/nn/driver/cache, hoặc bất kỳ cơ chế phù hợp nào khác để triển khai trình quản lý ánh xạ. Khi cập nhật trình điều khiển, bạn phải khởi động lại trình quản lý ánh xạ này để ngăn việc chuẩn bị các tệp được lưu vào bộ nhớ đệm từ phiên bản trước.

Để ngăn các cuộc tấn công từ thời điểm kiểm tra đến thời điểm sử dụng (TOCTOU), trình điều khiển phải tính toán hàm băm đã ghi trước khi lưu vào tệp và tính toán hàm băm mới sau khi sao chép nội dung tệp vào bộ đệm bên trong.

Mã mẫu này minh hoạ cách triển khai logic này.

bool saveToCache(const sp<V1_2::IPreparedModel> preparedModel,
                 const hidl_vec<hidl_handle>& modelFds, const hidl_vec<hidl_handle>& dataFds,
                 const HidlToken& token) {
    // Serialize the prepared model to internal buffers.
    auto buffers = serialize(preparedModel);

    // This implementation detail is important: the cache hash must be computed from internal
    // buffers instead of cache files to prevent time-of-check to time-of-use (TOCTOU) attacks.
    auto hash = computeHash(buffers);

    // Store the {token, hash} pair to a mapping manager that is persistent across reboots.
    CacheManager::get()->store(token, hash);

    // Write the cache contents from internal buffers to cache files.
    return writeToFds(buffers, modelFds, dataFds);
}

sp<V1_2::IPreparedModel> prepareFromCache(const hidl_vec<hidl_handle>& modelFds,
                                          const hidl_vec<hidl_handle>& dataFds,
                                          const HidlToken& token) {
    // Copy the cache contents from cache files to internal buffers.
    auto buffers = readFromFds(modelFds, dataFds);

    // This implementation detail is important: the cache hash must be computed from internal
    // buffers instead of cache files to prevent time-of-check to time-of-use (TOCTOU) attacks.
    auto hash = computeHash(buffers);

    // Validate the {token, hash} pair by a mapping manager that is persistent across reboots.
    if (CacheManager::get()->validate(token, hash)) {
        // Retrieve the prepared model from internal buffers.
        return deserialize<V1_2::IPreparedModel>(buffers);
    } else {
        return nullptr;
    }
}

Các trường hợp sử dụng nâng cao

Trong một số trường hợp sử dụng nâng cao, trình điều khiển cần truy cập vào nội dung bộ nhớ đệm (đọc hoặc ghi) sau lệnh gọi biên dịch. Ví dụ về trường hợp sử dụng:

  • Biên dịch vừa kịp lúc: Quá trình biên dịch bị trì hoãn cho đến lần thực thi đầu tiên.
  • Biên dịch nhiều giai đoạn: Quá trình biên dịch nhanh được thực hiện ban đầu và quá trình biên dịch được tối ưu hoá không bắt buộc sẽ được thực hiện vào thời điểm sau đó tuỳ thuộc vào tần suất sử dụng.

Để truy cập vào nội dung bộ nhớ đệm (đọc hoặc ghi) sau lệnh gọi biên dịch, hãy đảm bảo rằng trình điều khiển:

  • Sao chép các trình xử lý tệp trong quá trình gọi prepareModel_1_2 hoặc prepareModelFromCache và đọc/cập nhật nội dung bộ nhớ đệm vào thời điểm sau đó.
  • Triển khai logic khoá tệp bên ngoài lệnh gọi biên dịch thông thường để ngăn việc ghi xảy ra đồng thời với việc đọc hoặc một lần ghi khác.

Triển khai công cụ lưu vào bộ nhớ đệm

Ngoài giao diện lưu vào bộ nhớ đệm quá trình biên dịch NN HAL 1.2, bạn cũng có thể tìm thấy thư viện tiện ích lưu vào bộ nhớ đệm trong thư mục frameworks/ml/nn/driver/cache. Thư mục con nnCache chứa mã bộ nhớ liên tục để trình điều khiển triển khai việc lưu vào bộ nhớ đệm quá trình biên dịch mà không cần sử dụng các tính năng lưu vào bộ nhớ đệm của NNAPI. Bạn có thể triển khai hình thức lưu vào bộ nhớ đệm quá trình biên dịch này bằng mọi phiên bản của NN HAL. Nếu trình điều khiển chọn triển khai việc lưu vào bộ nhớ đệm không kết nối với giao diện HAL, thì trình điều khiển chịu trách nhiệm giải phóng các cấu phần phần mềm được lưu vào bộ nhớ đệm khi không còn cần nữa.