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

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

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

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

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

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

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

ข้อมูลแคชที่ระบุและแคชหายไป

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

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

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

ข้อมูลแคช

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

โทเค็น

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

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

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

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

ผู้ขับขี่ต้องกำหนดวิธีกระจายข้อมูลแคชระหว่างไฟล์แคชทั้ง 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 ไดรเวอร์ก็มีหน้าที่เพิ่มพื้นที่ว่างในอาร์ติแฟกต์ที่แคชไว้เมื่อไม่จำเป็นต้องใช้อีกต่อไป