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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ติดตั้งเครื่องมือแคช

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