Sebagian besar model AI memiliki satu kesamaan, yaitu model
cukup besar untuk resource yang
ditransfer melalui Internet. Model deteksi objek MediaPipe terkecil
(SSD MobileNetV2 float16
) berukuran 5,6 MB
dan yang terbesar berukuran sekitar 25 MB.
LLM open source gemma-2b-it-gpu-int4.bin
memiliki clock 1,35 GB—dan ini dianggap sangat kecil untuk LLM.
Model AI generatif bisa sangat besar. Itulah sebabnya banyak penggunaan AI saat ini terjadi
di cloud. Semakin banyak aplikasi yang menjalankan model yang dioptimalkan secara langsung
di perangkat. Meskipun demo LLM yang berjalan di browser
ada, berikut beberapa contoh model lain tingkat produksi yang berjalan di
browser:
- Adobe Photoshop menjalankan varian model
Conv2D
di perangkat untuk alat pemilihan objek cerdasnya. - Google Meet menjalankan versi model
MobileNetV3-small
yang dioptimalkan untuk segmentasi orang untuk fitur pemburaman latar belakangnya. - Tokopedia menjalankan model
MediaPipeFaceDetector-TFJS
untuk deteksi wajah real-time guna mencegah pendaftaran yang tidak valid ke layanannya. - Google Colab memungkinkan pengguna menggunakan model dari hard disk mereka di notebook Colab.
Agar peluncuran aplikasi di masa mendatang lebih cepat, Anda harus menyimpan cache data model secara eksplisit di perangkat, bukan mengandalkan cache browser HTTP implisit.
Meskipun panduan ini menggunakan gemma-2b-it-gpu-int4.bin model
untuk membuat chatbot,
pendekatan ini dapat digeneralisasi agar sesuai dengan model lain dan kasus penggunaan lainnya
di perangkat. Cara paling umum untuk menghubungkan aplikasi ke model adalah dengan menayangkan
model bersama dengan resource aplikasi lainnya. Penting untuk mengoptimalkan
pengiriman.
Mengonfigurasi header cache yang tepat
Jika menayangkan model AI dari server, penting untuk mengonfigurasi header
Cache-Control
yang benar. Contoh berikut menunjukkan setelan default yang solid, yang dapat Anda build
untuk kebutuhan aplikasi.
Cache-Control: public, max-age=31536000, immutable
Setiap versi model AI yang dirilis adalah resource statis. Konten yang tidak pernah
berubah harus diberi
max-age
yang panjang
dan digabungkan dengan cache busting
di URL permintaan. Jika perlu memperbarui model, Anda harus
memberinya URL baru.
Saat pengguna memuat ulang halaman, klien akan mengirimkan permintaan validasi ulang, meskipun
server mengetahui bahwa konten tersebut stabil. Perintah
immutable
secara eksplisit menunjukkan bahwa validasi ulang tidak diperlukan, karena
konten tidak akan berubah. Perintah immutable
tidak didukung secara luas
oleh browser dan server proxy atau cache perantara, tetapi dengan
menggabungkannya dengan
perintah max-age
yang dipahami secara universal, Anda dapat memastikan kompatibilitas
maksimum. Perintah respons public
menunjukkan bahwa respons dapat disimpan dalam cache bersama.
Meng-cache model AI sisi klien
Saat Anda menayangkan model AI, penting untuk menyimpan model secara eksplisit dalam browser. Hal ini memastikan data model siap tersedia setelah pengguna memuat ulang aplikasi.
Ada sejumlah teknik yang dapat Anda gunakan untuk mencapai hal ini. Untuk contoh kode
berikut, asumsikan setiap file model disimpan dalam
objek Blob
bernama blob
di memori.
Untuk memahami performa, setiap contoh kode dianotasi dengan metode
performance.mark()
dan performance.measure()
. Pengukuran ini bergantung pada perangkat dan tidak dapat digeneralisasi.
Anda dapat memilih untuk menggunakan salah satu API berikut untuk meng-cache model AI di browser: Cache API, Origin Private File System API, dan IndexedDB API. Rekomendasi umumnya adalah menggunakan Cache API, tetapi panduan ini membahas kelebihan dan kekurangan semua opsi.
API Cache
Cache API menyediakan
penyimpanan persisten untuk pasangan objek
Request
dan Response
yang di-cache dalam memori yang berumur panjang. Meskipun
ditentukan dalam spesifikasi Pekerja Layanan,
Anda dapat menggunakan API ini dari thread utama atau pekerja reguler. Untuk menggunakannya di luar
konteks pekerja layanan, panggil
metode Cache.put()
dengan objek Response
sintetis, yang disambungkan dengan URL sintetis, bukan
objek Request
.
Panduan ini mengasumsikan blob
dalam memori. Gunakan URL palsu sebagai kunci cache dan
Response
sintetis berdasarkan blob
. Jika Anda ingin mendownload
model secara langsung, Anda akan menggunakan Response
yang akan Anda dapatkan dari membuat permintaan
fetch()
.
Misalnya, berikut cara menyimpan dan memulihkan file model dengan Cache API.
const storeFileInSWCache = async (blob) => {
try {
performance.mark('start-sw-cache-cache');
const modelCache = await caches.open('models');
await modelCache.put('model.bin', new Response(blob));
performance.mark('end-sw-cache-cache');
const mark = performance.measure(
'sw-cache-cache',
'start-sw-cache-cache',
'end-sw-cache-cache'
);
console.log('Model file cached in sw-cache.', mark.name, mark.duration.toFixed(2));
} catch (err) {
console.error(err.name, err.message);
}
};
const restoreFileFromSWCache = async () => {
try {
performance.mark('start-sw-cache-restore');
const modelCache = await caches.open('models');
const response = await modelCache.match('model.bin');
if (!response) {
throw new Error(`File model.bin not found in sw-cache.`);
}
const file = await response.blob();
performance.mark('end-sw-cache-restore');
const mark = performance.measure(
'sw-cache-restore',
'start-sw-cache-restore',
'end-sw-cache-restore'
);
console.log(mark.name, mark.duration.toFixed(2));
console.log('Cached model file found in sw-cache.');
return file;
} catch (err) {
throw err;
}
};
Origin Private File System API
Origin Private File System (OPFS) adalah standar yang relatif baru untuk endpoint penyimpanan. File ini bersifat pribadi untuk asal halaman, sehingga tidak terlihat oleh pengguna, tidak seperti sistem file reguler. API ini memberikan akses ke file khusus yang sangat dioptimalkan untuk performa dan menawarkan akses tulis ke kontennya.
Misalnya, berikut cara menyimpan dan memulihkan file model di OPFS.
const storeFileInOPFS = async (blob) => {
try {
performance.mark('start-opfs-cache');
const root = await navigator.storage.getDirectory();
const handle = await root.getFileHandle('model.bin', { create: true });
const writable = await handle.createWritable();
await blob.stream().pipeTo(writable);
performance.mark('end-opfs-cache');
const mark = performance.measure(
'opfs-cache',
'start-opfs-cache',
'end-opfs-cache'
);
console.log('Model file cached in OPFS.', mark.name, mark.duration.toFixed(2));
} catch (err) {
console.error(err.name, err.message);
}
};
const restoreFileFromOPFS = async () => {
try {
performance.mark('start-opfs-restore');
const root = await navigator.storage.getDirectory();
const handle = await root.getFileHandle('model.bin');
const file = await handle.getFile();
performance.mark('end-opfs-restore');
const mark = performance.measure(
'opfs-restore',
'start-opfs-restore',
'end-opfs-restore'
);
console.log('Cached model file found in OPFS.', mark.name, mark.duration.toFixed(2));
return file;
} catch (err) {
throw err;
}
};
IndexedDB API
IndexedDB adalah standar yang sudah mapan untuk menyimpan data arbitrer secara persisten di browser. IndexedDB dikenal karena API-nya yang agak rumit, tetapi dengan menggunakan library wrapper seperti idb-keyval, Anda dapat memperlakukan IndexedDB seperti penyimpanan nilai kunci klasik.
Contoh:
import { get, set } from 'https://cdn.jsdelivr.net/npm/idb-keyval@latest/+esm';
const storeFileInIDB = async (blob) => {
try {
performance.mark('start-idb-cache');
await set('model.bin', blob);
performance.mark('end-idb-cache');
const mark = performance.measure(
'idb-cache',
'start-idb-cache',
'end-idb-cache'
);
console.log('Model file cached in IDB.', mark.name, mark.duration.toFixed(2));
} catch (err) {
console.error(err.name, err.message);
}
};
const restoreFileFromIDB = async () => {
try {
performance.mark('start-idb-restore');
const file = await get('model.bin');
if (!file) {
throw new Error('File model.bin not found in IDB.');
}
performance.mark('end-idb-restore');
const mark = performance.measure(
'idb-restore',
'start-idb-restore',
'end-idb-restore'
);
console.log('Cached model file found in IDB.', mark.name, mark.duration.toFixed(2));
return file;
} catch (err) {
throw err;
}
};
Menandai penyimpanan sebagai dipertahankan
Panggil navigator.storage.persist()
di akhir salah satu metode penyimpanan dalam cache ini untuk meminta izin guna menggunakan
penyimpanan persisten. Metode ini menampilkan promise yang me-resolve ke true
jika izin diberikan, dan false
jika tidak. Browser
mungkin memenuhi atau tidak memenuhi permintaan,
bergantung pada aturan khusus browser.
if ('storage' in navigator && 'persist' in navigator.storage) {
try {
const persistent = await navigator.storage.persist();
if (persistent) {
console.log("Storage will not be cleared except by explicit user action.");
return;
}
console.log("Storage may be cleared under storage pressure.");
} catch (err) {
console.error(err.name, err.message);
}
}
Kasus khusus: Menggunakan model pada hard disk
Anda dapat mereferensikan model AI langsung dari hard disk pengguna sebagai alternatif penyimpanan browser. Teknik ini dapat membantu aplikasi yang berfokus pada riset menunjukkan kemungkinan menjalankan model tertentu di browser, atau memungkinkan artis menggunakan model mandiri dalam aplikasi kreativitas dari pakar.
File System Access API
Dengan File System Access API, Anda dapat membuka file dari hard disk dan mendapatkan FileSystemFileHandle yang dapat Anda pertahankan ke IndexedDB.
Dengan pola ini, pengguna hanya perlu memberikan akses ke file model satu kali. Berkat izin yang dipertahankan,
pengguna dapat memilih untuk memberikan akses ke file secara permanen. Setelah memuat ulang
aplikasi dan gestur pengguna yang diperlukan, seperti klik mouse, FileSystemFileHandle
dapat dipulihkan dari Nexus dengan akses ke file
di hard disk.
Izin akses file dikueri dan diminta jika diperlukan, sehingga proses ini berjalan lancar untuk pemuatan ulang pada masa mendatang. Contoh berikut menunjukkan cara mendapatkan handle untuk file dari hard disk, lalu menyimpan dan memulihkan handle tersebut.
import { fileOpen } from 'https://cdn.jsdelivr.net/npm/browser-fs-access@latest/dist/index.modern.js';
import { get, set } from 'https://cdn.jsdelivr.net/npm/idb-keyval@latest/+esm';
button.addEventListener('click', async () => {
try {
const file = await fileOpen({
extensions: ['.bin'],
mimeTypes: ['application/octet-stream'],
description: 'AI model files',
});
if (file.handle) {
// It's an asynchronous method, but no need to await it.
storeFileHandleInIDB(file.handle);
}
return file;
} catch (err) {
if (err.name !== 'AbortError') {
console.error(err.name, err.message);
}
}
});
const storeFileHandleInIDB = async (handle) => {
try {
performance.mark('start-file-handle-cache');
await set('model.bin.handle', handle);
performance.mark('end-file-handle-cache');
const mark = performance.measure(
'file-handle-cache',
'start-file-handle-cache',
'end-file-handle-cache'
);
console.log('Model file handle cached in IDB.', mark.name, mark.duration.toFixed(2));
} catch (err) {
console.error(err.name, err.message);
}
};
const restoreFileFromFileHandle = async () => {
try {
performance.mark('start-file-handle-restore');
const handle = await get('model.bin.handle');
if (!handle) {
throw new Error('File handle model.bin.handle not found in IDB.');
}
if ((await handle.queryPermission()) !== 'granted') {
const decision = await handle.requestPermission();
if (decision === 'denied' || decision === 'prompt') {
throw new Error(Access to file model.bin.handle not granted.');
}
}
const file = await handle.getFile();
performance.mark('end-file-handle-restore');
const mark = performance.measure(
'file-handle-restore',
'start-file-handle-restore',
'end-file-handle-restore'
);
console.log('Cached model file handle found in IDB.', mark.name, mark.duration.toFixed(2));
return file;
} catch (err) {
throw err;
}
};
Metode ini tidak saling eksklusif. Mungkin ada kasus saat Anda secara eksplisit menyimpan model dalam cache di browser dan menggunakan model dari hard disk pengguna.
Demo
Anda dapat melihat ketiga metode penyimpanan kasus reguler dan metode hard disk yang diterapkan dalam demo LLM MediaPipe.
Bonus: Mendownload file besar dalam potongan
Jika Anda perlu mendownload model AI besar dari Internet, paralelkan download ke dalam beberapa bagian terpisah, lalu gabungkan kembali di klien.
Berikut adalah fungsi bantuan yang dapat Anda gunakan dalam kode. Anda hanya perlu meneruskan url
ke sini. chunkSize
(default: 5 MB), maxParallelRequests
(default: 6), fungsi progressCallback
(yang melaporkan tentang
downloadedBytes
dan total fileSize
), dan signal
untuk
sinyal AbortSignal
semuanya bersifat opsional.
Anda dapat menyalin fungsi berikut dalam project atau
menginstal paket fetch-in-chunks
dari paket npm.
async function fetchInChunks(
url,
chunkSize = 5 * 1024 * 1024,
maxParallelRequests = 6,
progressCallback = null,
signal = null
) {
// Helper function to get the size of the remote file using a HEAD request
async function getFileSize(url, signal) {
const response = await fetch(url, { method: 'HEAD', signal });
if (!response.ok) {
throw new Error('Failed to fetch the file size');
}
const contentLength = response.headers.get('content-length');
if (!contentLength) {
throw new Error('Content-Length header is missing');
}
return parseInt(contentLength, 10);
}
// Helper function to fetch a chunk of the file
async function fetchChunk(url, start, end, signal) {
const response = await fetch(url, {
headers: { Range: `bytes=${start}-${end}` },
signal,
});
if (!response.ok && response.status !== 206) {
throw new Error('Failed to fetch chunk');
}
return await response.arrayBuffer();
}
// Helper function to download chunks with parallelism
async function downloadChunks(
url,
fileSize,
chunkSize,
maxParallelRequests,
progressCallback,
signal
) {
let chunks = [];
let queue = [];
let start = 0;
let downloadedBytes = 0;
// Function to process the queue
async function processQueue() {
while (start < fileSize) {
if (queue.length < maxParallelRequests) {
let end = Math.min(start + chunkSize - 1, fileSize - 1);
let promise = fetchChunk(url, start, end, signal)
.then((chunk) => {
chunks.push({ start, chunk });
downloadedBytes += chunk.byteLength;
// Update progress if callback is provided
if (progressCallback) {
progressCallback(downloadedBytes, fileSize);
}
// Remove this promise from the queue when it resolves
queue = queue.filter((p) => p !== promise);
})
.catch((err) => {
throw err;
});
queue.push(promise);
start += chunkSize;
}
// Wait for at least one promise to resolve before continuing
if (queue.length >= maxParallelRequests) {
await Promise.race(queue);
}
}
// Wait for all remaining promises to resolve
await Promise.all(queue);
}
await processQueue();
return chunks.sort((a, b) => a.start - b.start).map((chunk) => chunk.chunk);
}
// Get the file size
const fileSize = await getFileSize(url, signal);
// Download the file in chunks
const chunks = await downloadChunks(
url,
fileSize,
chunkSize,
maxParallelRequests,
progressCallback,
signal
);
// Stitch the chunks together
const blob = new Blob(chunks);
return blob;
}
export default fetchInChunks;
Memilih metode yang tepat untuk Anda
Panduan ini telah mempelajari berbagai metode untuk menyimpan model AI secara efektif di browser, tugas yang penting untuk meningkatkan pengalaman pengguna dan performa aplikasi Anda. Tim penyimpanan Chrome merekomendasikan Cache API untuk performa yang optimal, guna memastikan akses cepat ke model AI, mengurangi waktu pemuatan, dan meningkatkan responsivitas.
OPFS dan IndexedDB adalah opsi yang kurang dapat digunakan. OPFS dan API IndexedDB perlu melakukan serialisasi data sebelum dapat disimpan. IndexedDB juga perlu melakukan deserialisasi data saat diambil, sehingga menjadikannya tempat terburuk untuk menyimpan model besar.
Untuk aplikasi khusus, File System Access API menawarkan akses langsung ke file di perangkat pengguna, yang ideal untuk pengguna yang mengelola model AI mereka sendiri.
Jika Anda perlu mengamankan model AI, simpan model tersebut di server. Setelah disimpan di klien, mengekstrak data dari Cache dan IndexedDB dengan DevTools atau ekstensi DevTools OFPS sangat mudah. API penyimpanan ini pada dasarnya memiliki keamanan yang sama. Anda mungkin tergoda untuk menyimpan versi model terenkripsi, tetapi Anda harus mendapatkan kunci dekripsi ke klien, yang dapat disadap. Ini berarti upaya orang tidak bertanggung jawab untuk mencuri model Anda sedikit lebih sulit, tetapi bukan tidak mungkin.
Sebaiknya pilih strategi penyimpanan dalam cache yang sesuai dengan persyaratan aplikasi, perilaku target audiens, dan karakteristik model AI yang digunakan. Hal ini memastikan aplikasi Anda responsif dan andal dalam berbagai kondisi jaringan dan batasan sistem.
Ucapan terima kasih
Ulasan ini diulas oleh Joshua Bell, Reilly Grant, Evan Stade, Nathan Memmott, Austin Sullivan, Etienne Noël, André Bandarra, Alexandra Klepper, François Beaufort, Paul Kinlan, dan Rachel Andrew.