การแคชการคอมไพล์

ตั้งแต่ Android 10 เป็นต้นไป Neural Networks API (NNAPI) มีฟังก์ชันที่รองรับ การแคชอาร์ติแฟกต์การคอมไพล์ ซึ่งช่วยลดเวลาที่ใช้ในการคอมไพล์ เมื่อแอปเริ่มต้น เมื่อใช้ฟังก์ชันการแคชนี้ คนขับไม่จำเป็นต้องจัดการหรือล้างไฟล์ที่แคชไว้ ฟีเจอร์นี้เป็นฟีเจอร์เสริมที่ สามารถใช้กับ NN HAL 1.2 ได้ ดูข้อมูลเพิ่มเติมเกี่ยวกับฟังก์ชันนี้ได้ที่ ANeuralNetworksCompilation_setCaching

นอกจากนี้ ไดรเวอร์ยังสามารถใช้การแคชการคอมไพล์โดยไม่ขึ้นอยู่กับ NNAPI ได้ด้วย ซึ่งสามารถนำไปใช้ได้ไม่ว่าจะใช้ฟีเจอร์แคช NDK และ HAL ของ NNAPI หรือไม่ก็ตาม AOSP มีไลบรารียูทิลิตีระดับต่ำ (เครื่องมือแคช) ดูข้อมูลเพิ่มเติมได้ที่การติดตั้งใช้งานเครื่องมือแคช

ภาพรวมของเวิร์กโฟลว์

ส่วนนี้จะอธิบายเวิร์กโฟลว์ทั่วไปที่มีการใช้ฟีเจอร์แคชการคอมไพล์

ระบุข้อมูลแคชและแคชที่ตรงกัน

  1. แอปจะส่งผ่านไดเรกทอรีแคชและผลรวมตรวจสอบที่ไม่ซ้ำกันสำหรับโมเดล
  2. รันไทม์ NNAPI จะค้นหาไฟล์แคชตามผลรวมตรวจสอบ ค่ากำหนดการดำเนินการ และผลลัพธ์การแบ่งพาร์ติชัน แล้วค้นหาไฟล์
  3. NNAPI จะเปิดไฟล์แคชและส่งผ่านแฮนเดิลไปยังไดรเวอร์ ด้วย prepareModelFromCache
  4. ไดรเวอร์จะเตรียมโมเดลจากไฟล์แคชโดยตรงและส่งคืน โมเดลที่เตรียมไว้

ข้อมูลแคชที่ระบุและการไม่พบแคช

  1. แอปจะส่ง Checksum ที่ไม่ซ้ำกันสำหรับโมเดลและแคช ไดเรกทอรี
  2. รันไทม์ NNAPI จะค้นหาไฟล์แคชตามผลรวมตรวจสอบ ค่ากำหนดการดำเนินการ และผลลัพธ์การแบ่งพาร์ติชัน แต่ไม่พบ ไฟล์แคช
  3. NNAPI จะสร้างไฟล์แคชที่ว่างเปล่าตามผลรวมตรวจสอบ ค่ากำหนดการดำเนินการ และการแบ่งพาร์ติชัน เปิดไฟล์แคช และส่งแฮนเดิลและโมเดลไปยังไดรเวอร์ด้วย prepareModel_1_2
  4. ไดรเวอร์จะคอมไพล์โมเดล เขียนข้อมูลการแคชลงในไฟล์แคช และส่งคืนโมเดลที่เตรียมไว้

ไม่ได้ระบุข้อมูลแคช

  1. แอปจะเรียกใช้การคอมไพล์โดยไม่มีข้อมูลการแคช
  2. แอปจะไม่ส่งข้อมูลใดๆ ที่เกี่ยวข้องกับการแคช
  3. รันไทม์ NNAPI จะส่งโมเดลไปยังไดรเวอร์พร้อมกับ prepareModel_1_2
  4. ไดรเวอร์จะคอมไพล์โมเดลและแสดงโมเดลที่เตรียมไว้

ข้อมูลแคช

ข้อมูลการแคชที่ให้แก่ไดรเวอร์ประกอบด้วยโทเค็นและ แฮนเดิลไฟล์แคช

โทเค็น

โทเค็น คือโทเค็นการแคชที่มีความยาว Constant::BYTE_SIZE_OF_CACHE_TOKEN ซึ่งระบุโมเดลที่เตรียมไว้ ระบบจะแสดงโทเค็นเดียวกันเมื่อบันทึกไฟล์แคชด้วย prepareModel_1_2 และเรียกข้อมูลโมเดลที่เตรียมไว้ด้วย prepareModelFromCache ไคลเอ็นต์ของไดรเวอร์ควรเลือกโทเค็นที่มี อัตราการชนต่ำ ไดรเวอร์ตรวจไม่พบการชนกันของโทเค็น การชนกัน ส่งผลให้การดำเนินการล้มเหลวหรือดำเนินการสำเร็จแต่ สร้างค่าเอาต์พุตที่ไม่ถูกต้อง

แฮนเดิลไฟล์แคช (ไฟล์แคช 2 ประเภท)

ไฟล์แคชมี 2 ประเภท ได้แก่ แคชข้อมูลและแคชโมเดล

  • แคชข้อมูล: ใช้สำหรับแคชข้อมูลคงที่ รวมถึงบัฟเฟอร์เทนเซอร์ที่ประมวลผลล่วงหน้าและ แปลงแล้ว การแก้ไขแคชข้อมูลไม่ควร ส่งผลกระทบที่แย่กว่าการสร้างค่าเอาต์พุตที่ไม่ถูกต้องในเวลา ดำเนินการ
  • แคชโมเดล: ใช้สำหรับแคชข้อมูลที่มีความละเอียดอ่อนด้านความปลอดภัย เช่น โค้ดแมชชีนที่คอมไพล์แล้วซึ่งเป็นไฟล์ปฏิบัติการ ในรูปแบบไบนารีดั้งเดิมของอุปกรณ์ การแก้ไขแคชโมเดลอาจส่งผลต่อลักษณะการทำงานของไดรเวอร์ และไคลเอ็นต์ที่เป็นอันตรายอาจใช้ประโยชน์จากสิ่งนี้เพื่อดำเนินการนอกเหนือจาก สิทธิ์ที่ได้รับ ดังนั้น ไดรเวอร์ต้องตรวจสอบว่าแคชโมเดล เสียหายหรือไม่ก่อนที่จะเตรียมโมเดลจากแคช ดูข้อมูลเพิ่มเติมได้ที่ความปลอดภัย

ไดรเวอร์ต้องตัดสินใจว่าจะกระจายข้อมูลแคชระหว่างไฟล์แคช 2 ประเภทอย่างไร และรายงานจำนวนไฟล์แคชที่ต้องการสำหรับแต่ละประเภท ด้วย getNumberOfCacheFilesNeeded

รันไทม์ของ NNAPI จะเปิดแฮนเดิลไฟล์แคชด้วยสิทธิ์ทั้งอ่านและเขียนเสมอ

ความปลอดภัย

ในการแคชการคอมไพล์ แคชโมเดลอาจมีข้อมูลที่ละเอียดอ่อนด้านความปลอดภัย เช่น โค้ดแมชชีนที่คอมไพล์แล้วซึ่งเรียกใช้งานได้ในรูปแบบไบนารีดั้งเดิมของอุปกรณ์ หากไม่ได้ป้องกันอย่างเหมาะสม การแก้ไขแคชโมเดลอาจส่งผลต่อลักษณะการทำงานของไดรเวอร์ เนื่องจากเนื้อหาแคชจะจัดเก็บไว้ในไดเรกทอรีแอป ไคลเอ็นต์จึงแก้ไขไฟล์แคชได้ ไคลเอ็นต์ที่มีข้อบกพร่องอาจ ทำให้แคชเสียหายโดยไม่ตั้งใจ และไคลเอ็นต์ที่เป็นอันตรายอาจจงใจ ใช้สิ่งนี้เพื่อเรียกใช้โค้ดที่ไม่ได้รับการยืนยันในอุปกรณ์ ซึ่งอาจเป็นปัญหาด้านความปลอดภัย ทั้งนี้ขึ้นอยู่กับ ลักษณะของอุปกรณ์ ดังนั้น ไดรเวอร์ต้องตรวจพบ การเสียหายของแคชโมเดลที่อาจเกิดขึ้นก่อนที่จะเตรียมโมเดลจากแคช

วิธีหนึ่งในการทำเช่นนี้คือให้คนขับดูแลแผนที่จากโทเค็นไปยัง แฮชที่เข้ารหัสของแคชโมเดล ไดรเวอร์สามารถจัดเก็บโทเค็นและ แฮชของแคชโมเดลเมื่อบันทึกการคอมไพล์ลงในแคช ไดรเวอร์จะตรวจสอบ แฮชใหม่ของแคชโมเดลกับคู่โทเค็นและแฮชที่บันทึกไว้เมื่อ เรียกข้อมูลการคอมไพล์จากแคช การแมปนี้ควรคงอยู่ตลอด การรีบูตระบบ ไดรเวอร์สามารถใช้บริการที่เก็บคีย์ของ Android, ไลบรารียูทิลิตีใน framework/ml/nn/driver/cache หรือกลไกอื่นๆ ที่เหมาะสมเพื่อใช้ตัวจัดการการแมป เมื่ออัปเดตไดรเวอร์แล้ว ควรเริ่มต้นตัวจัดการการแมปนี้ใหม่เพื่อป้องกันไม่ให้เตรียมไฟล์แคชจากเวอร์ชันก่อนหน้า

ไดรเวอร์ต้องคำนวณแฮชที่บันทึกไว้ก่อนบันทึกลงในไฟล์ และคำนวณแฮชใหม่หลังจากคัดลอกเนื้อหาไฟล์ไปยังบัฟเฟอร์ภายใน เพื่อป้องกันการโจมตีแบบ เวลาตรวจสอบถึงเวลาใช้งาน (TOCTOU)

โค้ดตัวอย่างนี้แสดงวิธีใช้ตรรกะนี้

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;
    }
}

Use Case ขั้นสูง

ในกรณีการใช้งานขั้นสูงบางอย่าง ไดรเวอร์ต้องเข้าถึงเนื้อหาแคช (อ่านหรือเขียน) หลังจากเรียกใช้การคอมไพล์ ตัวอย่างกรณีการใช้งานมีดังนี้

  • การคอมไพล์แบบทันที: การคอมไพล์จะล่าช้าจนกว่าจะมีการ ดำเนินการครั้งแรก
  • การคอมไพล์แบบหลายขั้นตอน: ระบบจะทำการคอมไพล์อย่างรวดเร็วในตอนแรก และจะทำการคอมไพล์ที่เพิ่มประสิทธิภาพ (ไม่บังคับ) ในภายหลัง โดยขึ้นอยู่กับความถี่ในการใช้งาน

หากต้องการเข้าถึงเนื้อหาแคช (อ่านหรือเขียน) หลังจากเรียกใช้การคอมไพล์ ให้ตรวจสอบว่าไดรเวอร์มีคุณสมบัติดังนี้

  • ทำซ้ำแฮนเดิลของไฟล์ระหว่างการเรียกใช้ prepareModel_1_2 หรือ prepareModelFromCache และอ่าน/อัปเดตเนื้อหาแคชในภายหลัง
  • ใช้ตรรกะการล็อกไฟล์นอกการเรียกใช้การคอมไพล์ปกติ เพื่อป้องกันไม่ให้มีการเขียนพร้อมกับการอ่านหรือการเขียนอื่น

ใช้เครื่องมือแคช

นอกจากอินเทอร์เฟซการแคชการคอมไพล์ NN HAL 1.2 แล้ว คุณยังดู ไลบรารียูทิลิตีการแคชได้ในไดเรกทอรี frameworks/ml/nn/driver/cache ไดเรกทอรีย่อย nnCache มีโค้ดพื้นที่เก็บข้อมูลแบบถาวรสำหรับไดรเวอร์เพื่อใช้ การแคชการคอมไพล์โดยไม่ต้องใช้ฟีเจอร์การแคช NNAPI การแคชการคอมไพล์รูปแบบนี้สามารถใช้ได้กับ NN HAL ทุกเวอร์ชัน หาก ไดรเวอร์เลือกใช้การแคชที่ไม่ได้เชื่อมต่อกับอินเทอร์เฟซ HAL ไดรเวอร์จะต้อง รับผิดชอบในการล้างอาร์ติแฟกต์ที่แคชไว้เมื่อไม่จำเป็นอีกต่อไป