Neural Networks API

Android Neural Networks API (NNAPI) adalah Android C API yang dirancang untuk menjalankan operasi komputasi intensif untuk machine learning di perangkat Android. NNAPI dirancang untuk menyediakan lapisan dasar fungsionalitas bagi framework machine learning tingkat tinggi, seperti TensorFlow Lite dan Caffe2, yang membangun dan melatih jaringan neural. API ini tersedia di semua perangkat yang menjalankan Android 8.1 (API level 27) atau lebih tinggi.

NNAPI mendukung inferensi dengan menerapkan data dari perangkat Android ke model yang telah dilatih sebelumnya dan ditetapkan oleh developer. Contoh inferensi mencakup mengklasifikasi gambar, memprediksi perilaku pengguna, dan memilih respons yang tepat untuk sebuah kueri penelusuran.

Inferensi pada perangkat memiliki banyak keuntungan:

  • Latensi: Anda tidak perlu mengirim permintaan melalui koneksi jaringan dan menunggu respons. Misalnya, ini sangat penting untuk aplikasi video yang memproses frame berurutan dari kamera.
  • Ketersediaan: Aplikasi dapat berjalan bahkan saat berada di luar jangkauan jaringan.
  • Kecepatan: Hardware baru yang khusus untuk pemrosesan jaringan neural menghasilkan komputasi yang jauh lebih cepat daripada CPU biasa.
  • Privasi: Data disimpan di perangkat Android.
  • Biaya: Tidak diperlukan farm server saat semua komputasi dilakukan pada perangkat Android.

Ada juga beberapa konsekuensi yang harus dipertimbangkan developer:

  • Pemanfaatan sistem: Evaluasi jaringan neural melibatkan banyak komputasi, yang dapat meningkatkan penggunaan daya baterai. Sebaiknya Anda mempertimbangkan pemantauan kesehatan baterai jika hal ini menjadi perhatian bagi aplikasi, terutama untuk komputasi yang berjalan lama.
  • Ukuran aplikasi: Perhatikan ukuran model Anda. Model dapat berukuran hingga beberapa megabyte. Jika pemaketan model besar dalam APK akan berdampak buruk bagi pengguna, Anda mungkin perlu mempertimbangkan untuk mendownload model setelah penginstalan aplikasi, menggunakan model yang lebih kecil, atau menjalankan komputasi di cloud. NNAPI tidak menyediakan fungsionalitas untuk menjalankan model di cloud.

Lihat sampel Android Neural Networks API untuk mengetahui satu contoh cara menggunakan NNAPI.

Memahami runtime Neural Networks API

NNAPI dimaksudkan untuk dipanggil oleh library, framework, dan alat machine learning yang memungkinkan developer melatih model di luar perangkat dan menerapkannya di perangkat Android. Aplikasi biasanya tidak langsung menggunakan NNAPI, tetapi secara langsung menggunakan framework machine learning dengan level yang lebih tinggi. Framework ini nantinya dapat menggunakan NNAPI untuk menjalankan operasi inferensi dengan akselerasi hardware pada perangkat yang didukung.

Berdasarkan persyaratan aplikasi dan kemampuan hardware perangkat, Android, runtime jaringan neural Android dapat secara efisien mendistribusikan beban kerja komputasi ke seluruh prosesor pada perangkat yang ada, termasuk hardware jaringan neural khusus, unit pemrosesan grafis (GPU), dan pemroses sinyal digital (DSP).

Untuk perangkat Android yang tidak memiliki driver vendor khusus, runtime NNAPI mengeksekusi permintaan pada CPU.

Gambar 1 menunjukkan arsitektur sistem tingkat tinggi untuk NNAPI.

Gambar 1. Arsitektur sistem untuk Android Neural Networks API

Model pemrograman Neural Networks API

Untuk menjalankan komputasi menggunakan NNAPI, pertama-tama Anda harus membuat grafik terarah yang akan menentukan komputasi yang dilakukan. Grafik komputasi ini, digabungkan dengan data input Anda (misalnya, bobot dan bias yang diturunkan dari framework machine learning), membentuk model untuk evaluasi runtime NNAPI.

NNAPI menggunakan empat abstraksi utama:

  • Model: Grafik komputasi dari operasi matematis dan nilai konstanta yang dipelajari melalui proses pelatihan. Operasi ini khusus untuk jaringan neural. Operasi ini mencakup konvolusi 2 dimensi (2D), aktivasi logistik (sigmoid) , aktivasi linear terkoreksi (ReLU) activation, dan lainnya. Pembuatan model adalah operasi sinkron. Setelah berhasil dibuat, model dapat digunakan kembali pada berbagai thread dan kompilasi. Pada NNAPI, model dinyatakan sebagai instance ANeuralNetworksModel .
  • Kompilasi: Menunjukkan konfigurasi untuk mengompilasi model NNAPI ke dalam kode dengan level lebih rendah. Pembuatan kompilasi adalah operasi sinkron. Setelah berhasil dibuat, kompilasi dapat digunakan kembali pada berbagai thread dan kompilasi. Pada NNAPI, tiap kompilasi dinyatakan sebagai instance ANeuralNetworksCompilation .
  • Memori: Menunjukkan memori bersama, file yang dipetakan memori, dan buffer memori serupa. Penggunaan buffer memori memungkinkan runtime NNAPI mentransfer data ke driver dengan lebih efisien. Sebuah aplikasi biasanya membuat satu buffer memori bersama yang berisi setiap tensor yang diperlukan untuk menetapkan model. Anda juga dapat menggunakan buffer memori untuk menyimpan input dan output untuk instance eksekusi. Pada NNAPI, tiap buffer memori dinyatakan sebagai instance ANeuralNetworksMemory .
  • Eksekusi: Antarmuka untuk menerapkan model NNAPI ke sekumpulan input dan mengumpulkan hasilnya. Eksekusi dapat dilakukan secara sinkron atau asinkron.

    Untuk eksekusi asinkron, beberapa thread dapat menunggu eksekusi yang sama. Setelah eksekusi selesai, semua thread akan dirilis.

    Pada NNAPI, tiap eksekusi dinyatakan sebagai instance ANeuralNetworksExecution .

Gambar 2 menunjukkan alur pemrograman dasar.

Gambar 2. Alur pemrograman untuk Android Neural Networks API

Bagian berikutnya menjelaskan langkah-langkah untuk menyiapkan model NNAPI guna melakukan komputasi, mengompilasi model, dan mengeksekusi model yang dikompilasi.

Menyediakan akses ke data pelatihan

Data bias dan bobot terlatih Anda kemungkinan tersimpan dalam sebuah file. Untuk menyediakan runtime NNAPI dengan akses yang efisien ke data ini, buat instance ANeuralNetworksMemory dengan memanggil fungsi ANeuralNetworksMemory_createFromFd() lalu meneruskan deskriptor file dari file data yang terbuka. Anda juga menetapkan flag proteksi memori dan offset di mana region memori bersama dimulai pada file tersebut.

// Create a memory buffer from the file that contains the trained data
    ANeuralNetworksMemory* mem1 = NULL;
    int fd = open("training_data", O_RDONLY);
    ANeuralNetworksMemory_createFromFd(file_size, PROT_READ, fd, 0, &mem1);
    

Meski dalam contoh ini kami hanya menggunakan satu instance ANeuralNetworksMemory untuk semua bobot, Anda dapat menggunakan lebih dari satu instance ANeuralNetworksMemory untuk beberapa file.

Menggunakan buffer hardware native

Anda dapat menggunakan buffer hardware native untuk input, output, dan nilai operand konstanta model. Dalam kasus tertentu, akselerator NNAPI dapat mengakses objek AHardwareBuffer tanpa driver perlu menyalin data. AHardwareBuffer memiliki banyak konfigurasi yang berbeda, dan tidak semua akselerator NNAPI dapat mendukung semua konfigurasi ini. Karena keterbatasan ini, lihat konstrain yang tercantum di ANeuralNetworksMemory_createFromAHardwareBuffer dokumentasi referensi dan uji terlebih dahulu di perangkat target untuk memastikan kompilasi dan eksekusi yang menggunakan AHardwareBuffer berperilaku seperti yang diharapkan, menggunakan penetapan perangkat untuk menentukan akselerator.

Untuk mengizinkan runtime NNAPI mengakses objek AHardwareBuffer, buat instance ANeuralNetworksMemory dengan memanggil fungsi ANeuralNetworksMemory_createFromAHardwareBuffer dan meneruskan objek , seperti ditunjukkan dalam contoh kode berikut:

    // Configure and create AHardwareBuffer object
    AHardwareBuffer_Desc desc = ...
    AHardwareBuffer* awhb = nullptr;
    AHardwareBuffer_allocate(&desc, &awhb);

    // Create ANeuralNetworksMemory from AHardwareBuffer
    ANeuralNetworksMemory* mem2 = NULL;
    ANeuralNetworksMemory_createFromAHardwareBuffer(ahwb, &mem2);
    

Saat NNAPI tidak perlu lagi mengakses objek AHardwareBuffer, kosongkan instance ANeuralNetworksMemory yang sesuai:

    ANeuralNetworksMemory_free(mem2);
    

Catatan:

  • Anda dapat menggunakan AHardwareBuffer hanya untuk seluruh buffer; Anda tidak dapat menggunakannya dengan parameter ARect.
  • Runtime NNAPI tidak akan mengosongkan buffer. Anda harus memastikan bahwa buffer input dan output dapat diakses sebelum menjadwalkan eksekusi.
  • Tidak ada dukungan untuk deskriptor file fence sinkronisasi.
  • Untuk AHardwareBuffer dengan format khusus vendor dan bit penggunaan, implementasi vendor akan menentukan apakah klien atau driver bertanggung jawab untuk mengosongkan cache.

Model

Model adalah unit dasar komputasi pada NNAPI. Tiap model ditentukan oleh satu atau beberapa operand dan operasi.

Operand

Operand adalah objek data yang digunakan dalam menetapkan grafik. Operand mencakup input dan output model, node perantara yang berisi data yang mengalir dari satu operasi ke operasi lainnya, dan konstanta yang diteruskan ke operasi tersebut.

Ada dua jenis operand yang dapat ditambahkan ke model NNAPI: skalar dan tensor.

Skalar mewakili sebuah nilai. NNAPI mendukung nilai skalar dalam boolean, titik mengambang 16-bit, titik mengambang 32-bit, bilangan bulat 32-bit, dan format bilangan bulat 32-bit yang tidak ditandatangani.

Sebagian besar operasi di NNAPI melibatkan tensor. Tensor adalah array n-dimensional. NNAPI mendukung tensor dengan titik mengambang 16-bit, titik mengambang 32-bit, nilai terkuantisasi 8-bit, nilai terkuantisasi 16-bit, bilangan bulat 32-bit, dan nilai boolean 8-bit.

Misalnya, gambar 3 menunjukkan model dengan dua operasi: penambahan diikuti dengan perkalian. Model ini mengambil sebuah tensor input dan menghasilkan satu tensor output.

Gambar 3. Contoh operand untuk model NNAPI

Model di atas memiliki tujuh operand. Operand tersebut diidentifikasi secara implisit oleh indeks dengan urutan seperti saat ditambahkan ke model. Operand pertama yang ditambahkan memiliki indeks 0, yang kedua memiliki indeks 1, dan seterusnya. Operan 1, 2, 3, dan 5 adalah operand konstanta.

Urutan Anda menambahkan operand tidak menjadi masalah. Misalnya, operand output model dapat menjadi yang pertama ditambahkan. Bagian penting adalah menggunakan nilai indeks yang tepat saat merujuk ke sebuah operand.

Operand memiliki beberapa jenis. Jenis ini ditetapkan saat ditambahkan ke model.

Sebuah operand tidak dapat digunakan sebagai input sekaligus output untuk sebuah model.

Setiap operand harus berupa input model, konstanta, atau operan output dari persis satu operasi.

Untuk informasi tambahan tentang penggunaan operand, lihat Selengkapnya tentang operand.

Operasi

Sebuah operasi menentukan komputasi yang akan dilakukan. Tiap operasi terdiri dari elemen-elemen ini:

  • jenis operasi (misalnya, penambahan, perkalian, konvolusi),
  • daftar indeks operand yang digunakan operasi untuk input, dan
  • daftar indeks operand yang digunakan operasi untuk output.

Urutan dalam daftar ini penting; lihat Referensi NNAPI API untuk input dan output yang diharapkan dari tiap jenis operasi.

Anda harus menambahkan operand yang dipakai atau dihasilkan oleh operasi ke model sebelum menambahkan operasi tersebut.

Urutan Anda menambahkan operasi tidak menjadi masalah. NNAPI mengandalkan dependensi yang ditetapkan oleh grafik komputasi operand dan operasi untuk menentukan urutan eksekusi operasi.

Operasi yang didukung oleh NNAPI diringkas dalam tabel di bawah:

Kategori Operasi
Operasi matematis seluruh elemen
Manipulasi tensor
Operasi gambar
Operasi pencarian
Operasi normalisasi
Operasi konvolusi
Operasi penggabungan
Operasi aktivasi
Operasi lainnya

Masalah umum di API level 28: Saat meneruskan tensor ANEURALNETWORKS_TENSOR_QUANT8_ASYMM ke operasi ANEURALNETWORKS_PAD , yang tersedia pada Android 9 (API level 28) dan lebih tinggi, output dari NNAPI mungkin tidak cocok dengan output dari framework machine learning level lebih tinggi, seperti TensorFlow Lite. Anda seharusnya hanya meneruskan ANEURALNETWORKS_TENSOR_FLOAT32. Masalah ini teratasi di Android 10 (API level 29) dan lebih tinggi.

Membuat model

Dalam contoh berikut, kami membuat model dua operasi yang terdapat pada gambar 3.

Untuk membuat model, ikuti langkah-langkah ini:

  1. Panggil fungsi ANeuralNetworksModel_create() untuk menetapkan model kosong.

        ANeuralNetworksModel* model = NULL;
        ANeuralNetworksModel_create(&model);
        
  2. Tambahkan operand ke model Anda dengan memanggil ANeuralNetworks_addOperand(). Jenis datanya ditetapkan menggunakan struktur data ANeuralNetworksOperandType .

        // In our example, all our tensors are matrices of dimension [3][4]
        ANeuralNetworksOperandType tensor3x4Type;
        tensor3x4Type.type = ANEURALNETWORKS_TENSOR_FLOAT32;
        tensor3x4Type.scale = 0.f;    // These fields are used for quantized tensors
        tensor3x4Type.zeroPoint = 0;  // These fields are used for quantized tensors
        tensor3x4Type.dimensionCount = 2;
        uint32_t dims[2] = {3, 4};
        tensor3x4Type.dimensions = dims;

    // We also specify operands that are activation function specifiers ANeuralNetworksOperandType activationType; activationType.type = ANEURALNETWORKS_INT32; activationType.scale = 0.f; activationType.zeroPoint = 0; activationType.dimensionCount = 0; activationType.dimensions = NULL;

    // Now we add the seven operands, in the same order defined in the diagram ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 0 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 1 ANeuralNetworksModel_addOperand(model, &activationType); // operand 2 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 3 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 4 ANeuralNetworksModel_addOperand(model, &activationType); // operand 5 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 6
  3. Untuk operand yang memiliki nilai konstanta, seperti bobot dan bias yang diperoleh aplikasi dari proses pelatihan, gunakan fungsi ANeuralNetworksModel_setOperandValue() dan ANeuralNetworksModel_setOperandValueFromMemory() .

    Dalam contoh berikut, kami menetapkan nilai konstanta dari file data pelatihan yang sesuai dengan buffer memori yang dibuat dalam Menyediakan akses ke data pelatihan.

        // In our example, operands 1 and 3 are constant tensors whose values were
        // established during the training process
        const int sizeOfTensor = 3 * 4 * 4;    // The formula for size calculation is dim0 * dim1 * elementSize
        ANeuralNetworksModel_setOperandValueFromMemory(model, 1, mem1, 0, sizeOfTensor);
        ANeuralNetworksModel_setOperandValueFromMemory(model, 3, mem1, sizeOfTensor, sizeOfTensor);

    // We set the values of the activation operands, in our example operands 2 and 5 int32_t noneValue = ANEURALNETWORKS_FUSED_NONE; ANeuralNetworksModel_setOperandValue(model, 2, &noneValue, sizeof(noneValue)); ANeuralNetworksModel_setOperandValue(model, 5, &noneValue, sizeof(noneValue));
  4. Untuk setiap operasi dalam grafik terarah yang ingin Anda hitung, tambahkan operasi ke model dengan memanggil fungsi ANeuralNetworksModel_addOperation() .

    Sebagai parameter untuk panggilan ini, aplikasi Anda harus menyediakan:

    • jenis operasi
    • jumlah nilai input
    • array indeks untuk operand input
    • jumlah nilai output
    • array indeks untuk operand output

    Perlu diperhatikan bahwa sebuah operand tidak dapat digunakan sebagai input sekaligus output untuk operasi yang sama.

        // We have two operations in our example
        // The first consumes operands 1, 0, 2, and produces operand 4
        uint32_t addInputIndexes[3] = {1, 0, 2};
        uint32_t addOutputIndexes[1] = {4};
        ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_ADD, 3, addInputIndexes, 1, addOutputIndexes);

    // The second consumes operands 3, 4, 5, and produces operand 6 uint32_t multInputIndexes[3] = {3, 4, 5}; uint32_t multOutputIndexes[1] = {6}; ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_MUL, 3, multInputIndexes, 1, multOutputIndexes);
  5. Identifikasi operand yang harus diperlakukan oleh model sebagai input dan outputnya, dengan memanggil fungsi ANeuralNetworksModel_identifyInputsAndOutputs() .

        // Our model has one input (0) and one output (6)
        uint32_t modelInputIndexes[1] = {0};
        uint32_t modelOutputIndexes[1] = {6};
        ANeuralNetworksModel_identifyInputsAndOutputs(model, 1, modelInputIndexes, 1 modelOutputIndexes);
        
  6. Jika mau, tentukan apakah ANEURALNETWORKS_TENSOR_FLOAT32 boleh dihitung dengan rentang atau presisi yang sama rendahnya dengan format titik mengambang 16-bit IEEE 754 dengan memanggil ANeuralNetworksModel_relaxComputationFloat32toFloat16().

  7. Panggil ANeuralNetworksModel_finish() untuk menyelesaikan penetapan model Anda. Jika tidak ada error, fungsi ini akan menampilkan kode hasil ANEURALNETWORKS_NO_ERROR.

        ANeuralNetworksModel_finish(model);
        

Setelah membuat model, Anda dapat mengompilasinya beberapa kali dan mengeksekusi tiap kompilasi beberapa kali.

Kompilasi

Langkah kompilasi menentukan prosesor mana yang akan digunakan untuk mengeksekusi model dan meminta driver terkait untuk mempersiapkan eksekusinya. Langkah ini dapat mencakup pembuatan kode mesin khusus untuk prosesor tempat model akan dijalankan.

Untuk mengompilasi model, ikuti langkah-langkah ini:

  1. Panggil fungsi ANeuralNetworksCompilation_create() untuk membuat instance kompilasi baru.

        // Compile the model
        ANeuralNetworksCompilation* compilation;
        ANeuralNetworksCompilation_create(model, &compilation);
        

    Jika mau, Anda dapat menggunakan penetapan perangkat untuk secara eksplisit memilih perangkat yang digunakan untuk mengeksekusi.

  2. Jika ingin, Anda dapat memengaruhi keseimbangan runtime antara penggunaan daya baterai dan kecepatan eksekusi. Anda dapat melakukannya dengan memanggil ANeuralNetworksCompilation_setPreference().

        // Ask to optimize for low power consumption
        ANeuralNetworksCompilation_setPreference(compilation, ANEURALNETWORKS_PREFER_LOW_POWER);
        

    Preferensi yang dapat Anda tentukan mencakup:

  3. Jika mau, Anda dapat menyiapkan penyimpanan cache kompilasi dengan memanggil ANeuralNetworksCompilation_setCaching.

        // Set up compilation caching
        ANeuralNetworksCompilation_setCaching(compilation, cacheDir, token);
        

    Gunakan getCodeCacheDir() untuk cacheDir. token yang ditentukan harus unik untuk tiap model dalam aplikasi.

  4. Selesaikan penetapan kompilasi dengan memanggil ANeuralNetworksCompilation_finish(). Jika tidak ada error, fungsi ini akan menampilkan kode hasil ANEURALNETWORKS_NO_ERROR.

        ANeuralNetworksCompilation_finish(compilation);
        

Penemuan dan penetapan perangkat

Di perangkat Android yang menjalankan Android 10 (API level 29) dan lebih tinggi, NNAPI menyediakan fungsi yang mengizinkan library framework machine learning dan aplikasi mendapatkan informasi tentang perangkat yang tersedia dan menentukan perangkat yang akan digunakan untuk eksekusi. Menyediakan informasi tentang perangkat yang tersedia memungkinkan aplikasi mendapatkan versi tepat driver yang terdapat di perangkat untuk menghindari ketidakcocokan umum. Dengan memberikan kemampuan kepada aplikasi untuk menentukan perangkat mana yang akan mengeksekusi berbagai bagian model, aplikasi dapat dioptimalkan untuk perangkat Android tempat diterapkannya aplikasi tersebut.

Penemuan perangkat

Gunakan ANeuralNetworks_getDeviceCount untuk mendapatkan jumlah perangkat yang tersedia. Untuk tiap perangkat, gunakan ANeuralNetworks_getDevice guna menetapkan instance ANeuralNetworksDevice untuk referensi kepada perangkat tersebut

Setelah memiliki referensi perangkat, Anda dapat mencari tahu informasi tambahan tentang perangkat tersebut menggunakan fungsi berikut:

Penetapan perangkat

Gunakan ANeuralNetworksModel_getSupportedOperationsForDevices untuk menemukan operasi model yang dapat dijalankan di perangkat tertentu.

Guna mengontrol akselerator yang digunakan untuk eksekusi, panggil ANeuralNetworksCompilation_createForDevices sebagai pengganti ANeuralNetworksCompilation_create. Gunakan objek ANeuralNetworksCompilation yang dihasilkan, seperti biasa. Fungsi menampilkan error jika model yang disediakan berisi operasi yang tidak didukung oleh perangkat yang dipilih.

Jika beberapa perangkat ditentukan, runtime bertanggung jawab untuk mendistribusikan pekerjaan di berbagai perangkat.

Serupa dengan perangkat lain, implementasi CPU NNAPI dinyatakan oleh ANeuralNetworksDevice dengan nama nnapi-reference dan jenis ANEURALNETWORKS_DEVICE_TYPE_CPU. Saat memanggil ANeuralNetworksCompilation_createForDevices, implementasi CPU tidak digunakan untuk menangani kasus kegagalan untuk kompilasi dan eksekusi model.

Aplikasi bertanggung jawab untuk membagi model menjadi sub-model yang dapat berjalan di perangkat yang ditentukan. Aplikasi yang tidak perlu melakukan partisi manual harus terus memanggil ANeuralNetworksCompilation_create yang sederhana untuk menggunakan semua perangkat yang tersedia (termasuk CPU) untuk mengakselerasi model. Jika model tidak dapat sepenuhnya didukung oleh perangkat yang Anda tentukan menggunakan ANeuralNetworksCompilation_createForDevices, ANEURALNETWORKS_BAD_DATA ditampilkan.

Membuat Partisi Model

Ketika ada beberapa perangkat yang tersedia untuk model, runtime NNAPI akan mendistribusikan pekerjaan ke seluruh perangkat. Misalnya, jika ada lebih dari satu perangkat yang diberikan ke ANeuralNetworksCompilation_createForDevices, semua yang ditentukan akan dipertimbangkan saat mengalokasikan pekerjaan tersebut. Perlu diketahui bahwa jika perangkat CPU tidak ada dalam daftar, eksekusi CPU akan dinonaktifkan. Saat menggunakan ANeuralNetworksCompilation_create, semua perangkat yang tersedia akan dipertimbangkan, termasuk CPU.

Distribusi dilakukan dengan memilih perangkat yang tersedia dari daftar, untuk setiap operasi dalam model, perangkat yang mendukung operasi, dan mendeklarasikan performa terbaik, yaitu waktu eksekusi tercepat atau penggunaan daya terendah, tergantung pada preferensi eksekusi yang ditentukan oleh klien. Algoritme pembuatan partisi ini tidak memperhitungkan kemungkinan ketidakefisienan yang disebabkan oleh IO antarprosesor yang berbeda; jadi, saat menentukan beberapa prosesor (baik secara eksplisit ketika menggunakan ANeuralNetworksCompilation_createForDevices maupun secara implisit menggunakan ANeuralNetworksCompilation_create), penting untuk memberikan profil pada aplikasi yang dihasilkan.

Untuk memahami cara model Anda dipartisi oleh NNAPI, cari pesan dalam log Android (pada level INFO dengan tag ExecutionPlan):

    ModelBuilder::findBestDeviceForEachOperation(op-name): device-index
    

op-name adalah nama deskriptif operasi dalam grafik dan device-index adalah indeks perangkat kandidat dalam daftar perangkat. Daftar ini merupakan input yang diberikan ke ANeuralNetworksCompilation_createForDevices, atau jika menggunakan ANeuralNetworksCompilation_createForDevices, daftar perangkat yang ditampilkan saat melakukan iterasi di semua perangkat menggunakan ANeuralNetworks_getDeviceCount dan ANeuralNetworks_getDevice.

Pesan (pada level INFO dengan tag ExecutionPlan):

    ModelBuilder::partitionTheWork: only one best device: device-name
    

Pesan ini menunjukkan bahwa seluruh grafik telah dipercepat pada device-name perangkat.

Eksekusi

Langkah eksekusi menerapkan model ke satu set input, dan menyimpan output komputasi ke satu atau beberapa buffer pengguna atau ruang memori yang dialokasikan oleh aplikasi Anda.

Untuk mengeksekusi model yang telah dikompilasi, ikuti langkah-langkah ini:

  1. Panggil fungsi ANeuralNetworksExecution_create() untuk membuat instance eksekusi baru.

        // Run the compiled model against a set of inputs
        ANeuralNetworksExecution* run1 = NULL;
        ANeuralNetworksExecution_create(compilation, &run1);
        
  2. Tentukan tempat aplikasi Anda akan membaca nilai input untuk komputasi. Aplikasi Anda dapat membaca nilai input dari buffer pengguna atau ruang memori yang dialokasikan dengan memanggil masing-masing dari ANeuralNetworksExecution_setInput() atau ANeuralNetworksExecution_setInputFromMemory().

        // Set the single input to our sample model. Since it is small, we won't use a memory buffer
        float32 myInput[3][4] = { ...the data... };
        ANeuralNetworksExecution_setInput(run1, 0, NULL, myInput, sizeof(myInput));
        
  3. Tentukan tempat aplikasi Anda akan menulis nilai output. Aplikasi Anda dapat menulis nilai output ke buffer pengguna atau ruang memori yang dialokasikan, dengan memanggil masing-masing dari ANeuralNetworksExecution_setOutput() atau ANeuralNetworksExecution_setOutputFromMemory().

        // Set the output
        float32 myOutput[3][4];
        ANeuralNetworksExecution_setOutput(run1, 0, NULL, myOutput, sizeof(myOutput));
        
  4. Jadwalkan waktu mulai eksekusi dengan memanggil fungsi ANeuralNetworksExecution_startCompute(). Jika tidak ada error, fungsi ini akan menampilkan kode hasil ANEURALNETWORKS_NO_ERROR.

        // Starts the work. The work proceeds asynchronously
        ANeuralNetworksEvent* run1_end = NULL;
        ANeuralNetworksExecution_startCompute(run1, &run1_end);
        
  5. Panggil fungsi ANeuralNetworksEvent_wait() untuk menunggu hingga eksekusi selesai. Jika eksekusi berhasil, fungsi ini akan menampilkan kode hasil ANEURALNETWORKS_NO_ERROR. Proses menunggu dapat dilakukan pada thread lain, bukan hanya di thread tempat eksekusi dimulai.

        // For our example, we have no other work to do and will just wait for the completion
        ANeuralNetworksEvent_wait(run1_end);
        ANeuralNetworksEvent_free(run1_end);
        ANeuralNetworksExecution_free(run1);
        
  6. Secara opsional, Anda dapat menerapkan serangkaian input lain ke model yang dikompilasi menggunakan instance kompilasi yang sama seperti untuk membuat instance ANeuralNetworksExecution baru.

        // Apply the compiled model to a different set of inputs
        ANeuralNetworksExecution* run2;
        ANeuralNetworksExecution_create(compilation, &run2);
        ANeuralNetworksExecution_setInput(run2, ...);
        ANeuralNetworksExecution_setOutput(run2, ...);
        ANeuralNetworksEvent* run2_end = NULL;
        ANeuralNetworksExecution_startCompute(run2, &run2_end);
        ANeuralNetworksEvent_wait(run2_end);
        ANeuralNetworksEvent_free(run2_end);
        ANeuralNetworksExecution_free(run2);
        

Eksekusi sinkron

Eksekusi asinkron menghabiskan waktu untuk menghasilkan dan menyinkronkan thread. Selain itu, latensi dapat menjadi sangat bervariasi, dengan keterlambatan terlama mencapai maksimal 500 mikrodetik antara waktu thread diberi tahu atau dibangunkan dan waktu thread pada akhirnya diikat ke core CPUS.

Untuk meningkatkan latensi, Anda dapat mengarahkan aplikasi untuk membuat panggilan inferensi tersinkron ke runtime. Panggilan tersebut hanya akan menampilkan hasil setelah inferensi selesai, bukan pada saat inferensi dimulai. Aplikasi tidak memanggil ANeuralNetworksExecution_startCompute untuk panggilan inferensi asinkron ke runtime, tetapi akan memanggil ANeuralNetworksExecution_compute untuk melakukan panggilan tersinkron ke runtime. Panggilan ke ANeuralNetworksExecution_compute tidak membutuhkan ANeuralNetworksEvent dan tidak dipasangkan dengan panggilan ke ANeuralNetworksEvent_wait.

Eksekusi burst

Di perangkat Android yang menjalankan Android 10 (API level 29) dan yang lebih tinggi, NNAPI mendukung eksekusi burst melalui objek ANeuralNetworksBurst. Eksekusi burst adalah urutan eksekusi dari kompilasi yang sama yang terjadi dengan cepat, seperti yang berjalan pada frame pengambilan gambar kamera atau sampel audio berturut-turut. Penggunaan objek ANeuralNetworksBurst dapat menghasilkan eksekusi yang lebih cepat, karena objek tersebut menunjukkan ke akselerator bahwa resource dapat digunakan kembali antareksekusi dan akselerator harus tetap dalam kondisi performa tinggi selama durasi burst.

ANeuralNetworksBurst hanya memberikan sedikit perubahan pada jalur eksekusi normal. Buat objek burst menggunakan ANeuralNetworksBurst_create seperti yang ditunjukkan dalam cuplikan kode berikut:

    // Create burst object to be reused across a sequence of executions
    ANeuralNetworksBurst* burst = NULL;
    ANeuralNetworksBurst_create(compilation, &burst);
    

Eksekusi burst dilakukan secara tersinkron. Namun, jangan gunakan ANeuralNetworksExecution_compute untuk melakukan setiap inferensi, tetapi pasangkan berbagai objek ANeuralNetworksExecution dengan ANeuralNetworksBurst yang sama dalam panggilan ke fungsi ANeuralNetworksExecution_burstCompute.

    // Create and configure first execution object
    // ...

    // Execute using the burst object
    ANeuralNetworksExecution_burstCompute(execution1, burst);

    // Use results of first execution and free the execution object
    // ...

    // Create and configure second execution object
    // ...

    // Execute using the same burst object
    ANeuralNetworksExecution_burstCompute(execution2, burst);

    // Use results of second execution and free the execution object
    // ...
    

Bebaskan objek ANeuralNetworksBurst dengan ANeuralNetworksBurst_free saat tidak lagi diperlukan.

    // Cleanup
    ANeuralNetworksBurst_free(burst);
    

Output berukuran dinamis

Untuk mendukung model yang ukuran output-nya bergantung pada data input, yaitu yang ukurannya tidak dapat ditentukan pada waktu eksekusi model, gunakan ANeuralNetworksExecution_getOutputOperandRank dan ANeuralNetworksExecution_getOutputOperandDimensions.

Contoh kode berikut menunjukkan cara melakukannya:

    // Get the rank of the output
    uint32_t myOutputRank = 0;
    ANeuralNetworksExecution_getOutputOperandRank(run1, 0, &myOutputRank);

    // Get the dimensions of the output
    std::vector<uint32_t> myOutputDimensions(myOutputRank);
    ANeuralNetworksExecution_getOutputOperandDimensions(run1, 0, myOutputDimensions.data());
    

Mengukur performa

Apabila ingin menentukan total waktu eksekusi melalui runtime, Anda dapat menggunakan API eksekusi tersinkron dan menghitung waktu yang dipakai oleh panggilan. Apabila ingin menentukan total waktu eksekusi melalui level stack software yang lebih rendah, Anda dapat menggunakan ANeuralNetworksExecution_setMeasureTiming dan ANeuralNetworksExecution_getDuration untuk mendapatkan:

  • waktu eksekusi pada akselerator (bukan di driver, yang dijalankan pada prosesor host).
  • waktu eksekusi pada driver, termasuk waktu pada akselerator.

Waktu eksekusi pada driver tidak mencakup overhead seperti pada runtime itu sendiri dan IPC yang diperlukan oleh runtime untuk berkomunikasi dengan driver.

API ini mengukur durasi antara peristiwa pekerjaan yang dikirimkan dan pekerjaan yang diselesaikan , bukan waktu yang dihabiskan oleh driver atau akselerator untuk melakukan inferensi, yang mungkin diganggu oleh peralihan konteks.

Misalnya, jika inferensi 1 dimulai, driver menghentikan pekerjaan untuk melakukan inferensi 2, lalu melanjutkan dan menyelesaikan inferensi 1, waktu eksekusi untuk inferensi 1 akan mencakup waktu saat pekerjaan dihentikan untuk melakukan inferensi 2.

Informasi penentuan waktu ini mungkin berguna untuk penerapan produksi aplikasi guna mengumpulkan telemetri untuk penggunaan offline. Anda dapat menggunakan data penentuan waktu guna mengubah aplikasi untuk performa yang lebih tinggi.

Saat menggunakan fungsi ini, perlu diingat berikut ini:

  • Mengumpulkan informasi penentuan waktu mungkin memiliki biaya performa.
  • Hanya driver yang dapat menghitung waktu yang dihabiskan pada driver itu sendiri atau pada akselerator, dengan tidak mencakup waktu yang dihabiskan dalam runtime NNAPI dan pada IPC.
  • Anda dapat menggunakan API ini hanya dengan ANeuralNetworksExecution yang dibuat dengan ANeuralNetworksCompilation_createForDevices dengan numDevices = 1.
  • Tidak diperlukan driver agar dapat melaporkan informasi penentuan waktu.

Pembersihan

Langkah pembersihan menangani pengosongan resource internal yang digunakan untuk komputasi.

    // Cleanup
    ANeuralNetworksCompilation_free(compilation);
    ANeuralNetworksModel_free(model);
    ANeuralNetworksMemory_free(mem1);
    

Pengelolaan Error dan Fallback CPU

Jika terjadi error pada waktu pembuatan partisi, jika driver gagal mengompilasi (sebagian dari) model, atau jika driver gagal mengeksekusi (sebagian dari) model yang dikompilasi, NNAPI mungkin akan melakukan fallback ke penerapan CPU-nya sendiri dari satu atau beberapa operasi.

Jika klien NNAPI berisi versi operasi yang dioptimalkan (misalnya, TFLite), akan lebih mudah untuk menonaktifkan fallback CPU dan menangani kegagalan dengan penerapan operasi yang dioptimalkan dari klien.

Di Android 10, jika kompilasi dilakukan menggunakan ANeuralNetworksCompilation_createForDevices, fallback CPU akan dinonaktifkan.

Di Android P, eksekusi NNAPI akan melakukan fallback ke CPU jika eksekusi pada driver gagal. Hal ini juga berlaku pada Android 10 saat ANeuralNetworksCompilation_create dipilih untuk digunakan daripada ANeuralNetworksCompilation_createForDevices.

Eksekusi pertama akan melakukan fallback untuk partisi tersebut, dan jika masih gagal, seluruh model di CPU akan dicoba ulang.

Jika pembuatan partisi atau kompilasi gagal, seluruh model akan dicoba pada CPU.

Ada situasi ketika beberapa operasi tidak didukung pada CPU, dan dalam situasi seperti ini, kompilasi atau eksekusi akan gagal, bukan melakukan fallback.

Bahkan, setelah menonaktifkan fallback CPU, mungkin masih ada operasi dalam model yang dijadwalkan pada CPU. Jika CPU termasuk dalam daftar prosesor yang diizinkan, dan merupakan satu-satunya prosesor yang mendukung operasi tersebut atau merupakan prosesor yang mengklaim performa terbaik untuk operasi itu, prosesor tersebut akan dipilih sebagai eksekutor utama (non-fallback).

Untuk memastikan tidak adanya eksekusi CPU, gunakan ANeuralNetworksCompilation_createForDevices saat mengecualikan nnapi-reference dari daftar perangkat. Dimulai dari Android P, Anda dapat menonaktifkan fallback pada waktu eksekusi di build DEBUG dengan menetapkan properti debug.nn.partition ke 2.

Selengkapnya tentang operand

Bagian berikut membahas topik lanjutan tentang penggunaan operand.

Tensor terkuantisasi

Tensor terkuantisasi adalah cara ringkas untuk menyatakan array n-dimensional dari nilai bilangan titik mengambang.

NNAPI mendukung tensor terkuantisasi asimetris 8-bit. Untuk tensor ini, nilai setiap sel dinyatakan dengan bilangan bulat 8-bit. Tensor terkait dengan skala dan nilai zeroPoint yang digunakan untuk mengonversi bilangan bulat 8-bit menjadi nilai bilangan titik mengambang yang dinyatakan.

Rumusnya adalah:

    (cellValue - zeroPoint) * scale
    

di mana nilai zeroPoint adalah bilangan bulat 32-bit dan skalanya adalah nilai bilangan titik mengambang 32-bit.

Dibanding tensor nilai bilangan titik mengambang 32-bit, tensor terkuantisasi 8-bit memiliki dua keunggulan:

  • Aplikasi Anda lebih kecil, karena bobot yang dilatih hanya berukuran seperempat tensor 32-bit.
  • Komputasi biasanya dapat dijalankan lebih cepat. Hal ini disebabkan kecilnya data yang perlu diambil dari memori dan efisiensi prosesor seperti DSP dalam melakukan perhitungan bilangan bulat.

Meski memungkinkan untuk mengubah model bilangan titik mengambang menjadi model terkuantisasi, pengalaman kami menunjukkan bahwa melatih model terkuantisasi secara langsung akan memberikan hasil lebih baik. Akibatnya, jaringan neural belajar untuk mengompensasi peningkatan granularitas dari masing-masing nilai. Untuk setiap tensor terkuantisasi, skala dan nilai zeroPoint ditetapkan selama proses pelatihan.

Di NNAPI, tentukan jenis tensor yang dikuantisasi dengan menetapkan jenis kolom struktur data ANeuralNetworksOperandType ke ANEURALNETWORKS_TENSOR_QUANT8_ASYMM. Selain itu, tentukan juga skala dan nilai zeroPoint tensor dalam struktur data tersebut.

Selain tensor terkuantisasi asimetris 8-bit, NNAPI mendukung berikut ini:

Operand opsional

Beberapa operasi, seperti ANEURALNETWORKS_LSH_PROJECTION, mengambil operand opsional. Untuk menunjukkan dalam model bahwa operand opsional dihilangkan, panggil fungsi ANeuralNetworksModel_setOperandValue(), dengan meneruskan NULL untuk buffer dan 0 untuk panjang.

Jika keputusan tentang ada atau tidaknya operand akan berbeda-beda untuk setiap eksekusi, tunjukkan bahwa operand dihilangkan menggunakan fungsi ANeuralNetworksExecution_setInput() atau ANeuralNetworksExecution_setOutput(), dengan meneruskan NULL untuk buffer dan 0 untuk panjang.

Tensor urutan yang tidak diketahui

Android 9 (API level 28) memperkenalkan operand model dengan dimensi yang tidak diketahui, tetapi dengan urutan (jumlah dimensi) yang diketahui. Android 10 (API level 29) memperkenalkan tensor dengan urutan yang tidak diketahui, seperti yang ditunjukkan dalam ANeuralNetworksOperandType.

Tolok ukur NNAPI

Tolok ukur NNAPI tersedia pada AOSP di platform/test/mlts/benchmark (aplikasi tolok ukur) dan platform/test/mlts/models (model dan set data).

Tolok ukur mengevaluasi latensi dan akurasi serta membandingkan driver ke pekerjaan yang sama yang dilakukan menggunakan Tensorflow Lite yang berjalan di CPU, untuk model dan set data yang sama.

Untuk menggunakan tolok ukur, lakukan tindakan berikut:

  1. Hubungkan perangkat Android target ke komputer, buka jendela terminal, dan pastikan perangkat dapat dijangkau melalui adb.

  2. Jika lebih dari satu perangkat Android yang terhubung, ekspor variabel lingkungan ANDROID_SERIAL perangkat target.

  3. Buka direktori sumber level teratas Android.

  4. Jalankan perintah berikut:

        lunch aosp_arm-userdebug # Or aosp_arm64-userdebug if available
        ./test/mlts/benchmark/build_and_run_benchmark.sh
        

    Di akhir proses tolok ukur, hasilnya akan disajikan sebagai halaman HTML yang diteruskan ke xdg-open.

Log NNAPI

NNAPI membuat informasi diagnostik yang berguna di log sistem. Untuk menganalisis log, gunakan utilitas logcat.

Aktifkan logging panjang NNAPI untuk fase atau komponen tertentu dengan menetapkan properti debug.nn.vlog (menggunakan adb shell) ke daftar nilai berikut, dipisahkan dengan spasi, titik dua, atau koma:

  • model: Pembuatan model
  • compilation: Pembuatan rencana eksekusi dan kompilasi model
  • execution: Eksekusi model
  • cpuexe: Eksekusi operasi menggunakan implementasi CPU NNAPI
  • manager: Ekstensi NNAPI, antarmuka yang tersedia, dan info yang terkait dengan kemampuan
  • all atau 1: Semua elemen di atas

Misalnya, untuk mengaktifkan logging panjang penuh, gunakan perintah adb shell setprop debug.nn.vlog all. Untuk menonaktifkan logging panjang, gunakan perintah adb shell setprop debug.nn.vlog '""'.

Setelah diaktifkan, logging panjang akan menghasilkan entri log pada level INFO dengan tag yang ditentukan sesuai nama komponen atau fase.

Di samping pesan yang dikontrol debug.nn.vlog, komponen NNAPI API menyediakan entri log lainnya di berbagai level, masing-masing menggunakan tag log tertentu.

Untuk mendapatkan daftar komponen, telusuri pohon sumber menggunakan ekspresi berikut:

grep -R 'define LOG_TAG' | awk -F '"' '{print $2}' | sort -u | egrep -v "Sample|FileTag|test"

Saat ini, ekspresi ini akan menampilkan tag berikut:

  • BurstBuilder
  • Callbacks
  • CompilationBuilder
  • CpuExecutor
  • ExecutionBuilder
  • ExecutionBurstController
  • ExecutionBurstServer
  • ExecutionPlan
  • FibonacciDriver
  • GraphDump
  • IndexedShapeWrapper
  • IonWatcher
  • Manager
  • Memory
  • MemoryUtils
  • MetaModel
  • ModelArgumentInfo
  • ModelBuilder
  • NeuralNetworks
  • OperationResolver
  • Operations
  • OperationsUtils
  • PackageInfo
  • TokenHasher
  • TypeManager
  • Utils
  • ValidateHal
  • VersionedInterfaces

Untuk mengontrol level pesan log yang ditampilkan oleh logcat, gunakan variabel lingkungan ANDROID_LOG_TAGS.

Untuk menampilkan kumpulan lengkap pesan log NNAPI dan menonaktifkan lainnya, tetapkan ANDROID_LOG_TAGS seperti berikut:

    BurstBuilder:V Callbacks:V CompilationBuilder:V CpuExecutor:V ExecutionBuilder:V ExecutionBurstController:V ExecutionBurstServer:V ExecutionPlan:V FibonacciDriver:V GraphDump:V IndexedShapeWrapper:V IonWatcher:V Manager:V MemoryUtils:V Memory:V MetaModel:V ModelArgumentInfo:V ModelBuilder:V NeuralNetworks:V OperationResolver:V OperationsUtils:V Operations:V PackageInfo:V TokenHasher:V TypeManager:V Utils:V ValidateHal:V VersionedInterfaces:V *:S.
    

Anda dapat menetapkan ANDROID_LOG_TAGS menggunakan perintah berikut:

    export ANDROID_LOG_TAGS=$(grep -R 'define LOG_TAG' | awk -F '"' '{ print $2 ":V" }' | sort -u | egrep -v "Sample|FileTag|test" | xargs echo -n; echo ' *:S')
    

Perlu diketahui bahwa kode ini hanyalah filter yang berlaku untuk logcat. Anda tetap harus menetapkan properti debug.nn.vlog ke all untuk menghasilkan info log panjang.