แคชการรวบรวม

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

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

ภาพรวมขั้นตอนการทำงาน

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

ข้อมูลแคชที่ให้ไว้และการเข้าถึงแคช

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

ข้อมูลแคชที่ให้มาและแคชพลาด

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

ตัวจัดการไฟล์แคช (ไฟล์แคชสองประเภท)

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

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

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

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

ความปลอดภัย

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

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

เพื่อป้องกันการโจมตี แบบ time-of-check ถึง time-of-use (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;
    }
}

กรณีการใช้งานขั้นสูง

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

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

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

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

ใช้กลไกการแคช

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