De meeste AI-modellen hebben minstens één ding gemeen: ze zijn vrij groot voor een hulpbron die via internet wordt verzonden. Het kleinste MediaPipe-objectdetectiemodel ( SSD MobileNetV2 float16
) weegt 5,6 MB en de grootste is ongeveer 25 MB.
De open-source LLM gemma-2b-it-gpu-int4.bin
klokt op 1,35 GB – en dit wordt als erg klein beschouwd voor een LLM. Generatieve AI-modellen kunnen enorm zijn. Dit is de reden waarom veel AI-gebruik tegenwoordig in de cloud plaatsvindt. Steeds vaker draaien apps sterk geoptimaliseerde modellen rechtstreeks op het apparaat. Hoewel er demo's bestaan van LLM's die in de browser draaien , zijn hier enkele voorbeelden van productiekwaliteit van andere modellen die in de browser draaien:
- Adobe Photoshop voert een variant van het
Conv2D
model uit op het apparaat voor zijn intelligente objectselectietool. - Google Meet voert een geoptimaliseerde versie uit van het
MobileNetV3-small
model voor persoonssegmentatie vanwege de functie voor achtergrondvervaging. - Tokopedia gebruikt het
MediaPipeFaceDetector-TFJS
model voor realtime gezichtsdetectie om ongeldige aanmeldingen voor zijn service te voorkomen. - Met Google Colab kunnen gebruikers modellen van hun harde schijf gebruiken in Colab-notebooks.
Om toekomstige lanceringen van uw applicaties sneller te laten verlopen, moet u de modelgegevens expliciet op het apparaat in de cache opslaan, in plaats van te vertrouwen op de impliciete HTTP-browsercache.
Hoewel deze handleiding het gemma-2b-it-gpu-int4.bin model
gebruikt om een chatbot te maken, kan de aanpak worden gegeneraliseerd voor andere modellen en andere gebruiksscenario's op het apparaat. De meest voorkomende manier om een app aan een model te koppelen, is door het model naast de rest van de app-bronnen te leveren. Het is cruciaal om de levering te optimaliseren.
Configureer de juiste cacheheaders
Als u AI-modellen vanaf uw server bedient, is het belangrijk om de juiste Cache-Control
header te configureren. Het volgende voorbeeld toont een solide standaardinstelling, waarop u kunt voortbouwen op de behoeften van uw app.
Cache-Control: public, max-age=31536000, immutable
Elke uitgebrachte versie van een AI-model is een statische bron. Inhoud die nooit verandert, moet een lange max-age
krijgen, gecombineerd met cachebusting in de verzoek-URL. Als u het model toch moet bijwerken, moet u het een nieuwe URL geven .
Wanneer de gebruiker de pagina opnieuw laadt, verzendt de client een hervalidatieverzoek, ook al weet de server dat de inhoud stabiel is. De immutable
richtlijn geeft expliciet aan dat hervalidatie niet nodig is, omdat de inhoud niet zal veranderen. De immutable
richtlijn wordt niet breed ondersteund door browsers en tussenliggende cache- of proxyservers, maar door deze te combineren met de universeel begrepen max-age
richtlijn kunt u maximale compatibiliteit garanderen. De public
responsrichtlijn geeft aan dat het antwoord kan worden opgeslagen in een gedeelde cache.
Cache AI-modellen aan de clientzijde
Wanneer u een AI-model aanbiedt, is het belangrijk om het model expliciet in de browser in de cache op te slaan. Dit zorgt ervoor dat de modelgegevens direct beschikbaar zijn nadat een gebruiker de app opnieuw heeft geladen.
Er zijn een aantal technieken die je kunt gebruiken om dit te bereiken. Voor de volgende codevoorbeelden gaan we ervan uit dat elk modelbestand is opgeslagen in een Blob
object met de naam blob
in het geheugen.
Om de prestaties te begrijpen, wordt elk codevoorbeeld geannoteerd met de methoden performance.mark()
en performance.measure()
. Deze maatregelen zijn apparaatafhankelijk en niet generaliseerbaar.
U kunt ervoor kiezen een van de volgende API's te gebruiken om AI-modellen in de browser in de cache op te slaan: Cache API , de Origin Private File System API en IndexedDB API . De algemene aanbeveling is om de Cache API te gebruiken , maar deze handleiding bespreekt de voor- en nadelen van alle opties.
Cache-API
De Cache API biedt permanente opslag voor Request
en Response
-objectparen die in het langlevende geheugen in de cache zijn opgeslagen. Hoewel het is gedefinieerd in de Service Workers-specificatie , kunt u deze API gebruiken vanuit de hoofdthread of vanuit een gewone werker. Als u deze buiten de context van een servicemedewerker wilt gebruiken, roept u de methode Cache.put()
aan met een synthetisch Response
object, gecombineerd met een synthetische URL in plaats van een Request
object.
In deze hand leiding wordt uitgegaan van een blob
in het geheugen. Gebruik een nep-URL als cachesleutel en een synthetisch Response
op basis van de blob
. Als u het model rechtstreeks zou downloaden, zou u de Response
gebruiken die u zou krijgen als u een fetch()
-verzoek zou doen.
Hier leest u bijvoorbeeld hoe u een modelbestand kunt opslaan en herstellen met de 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 privébestandssysteem-API
Het Origin Private File System (OPFS) is een relatief jonge standaard voor een opslageindpunt. Het is privé voor de oorsprong van de pagina en is dus onzichtbaar voor de gebruiker, in tegenstelling tot het reguliere bestandssysteem. Het biedt toegang tot een speciaal bestand dat sterk is geoptimaliseerd voor prestaties en biedt schrijftoegang tot de inhoud ervan.
Hier leest u bijvoorbeeld hoe u een modelbestand in de OPFS kunt opslaan en herstellen.
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;
}
};
GeïndexeerdeDB-API
IndexedDB is een gevestigde standaard voor het op een persistente manier opslaan van willekeurige gegevens in de browser. Het staat berucht om zijn ietwat complexe API, maar door een wrapperbibliotheek zoals idb-keyval te gebruiken, kun je IndexedDB behandelen als een klassieke sleutelwaardeopslag.
Bijvoorbeeld:
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;
}
};
Markeer de opslag als persistent
Roep navigator.storage.persist()
aan aan het einde van een van deze cachingmethoden om toestemming te vragen voor het gebruik van permanente opslag. Deze methode retourneert een belofte die wordt omgezet true
waar als toestemming wordt verleend, en anders false
. De browser kan het verzoek wel of niet honoreren , afhankelijk van browserspecifieke regels.
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);
}
}
Speciaal geval: Gebruik een model op een harde schijf
U kunt rechtstreeks vanaf de harde schijf van een gebruiker naar AI-modellen verwijzen als alternatief voor browseropslag. Deze techniek kan onderzoeksgerichte apps helpen de haalbaarheid van het uitvoeren van bepaalde modellen in de browser te demonstreren, of artiesten in staat stellen zelfgetrainde modellen te gebruiken in deskundige creativiteitsapps.
Toegang tot bestandssysteem-API
Met de File System Access API kunt u bestanden vanaf de harde schijf openen en een FileSystemFileHandle verkrijgen die u in IndexedDB kunt bewaren.
Bij dit patroon hoeft de gebruiker slechts één keer toegang te verlenen tot het modelbestand. Dankzij blijvende machtigingen kan de gebruiker ervoor kiezen om permanent toegang tot het bestand te verlenen. Na het opnieuw laden van de app en een vereist gebruikersgebaar, zoals een muisklik, kan de FileSystemFileHandle
worden hersteld vanuit IndexedDB met toegang tot het bestand op de harde schijf.
De toegangsrechten voor bestanden worden indien nodig opgevraagd en opgevraagd, waardoor dit naadloos verloopt voor toekomstige herlaadbeurten. In het volgende voorbeeld ziet u hoe u een handle voor een bestand van de harde schijf kunt ophalen en vervolgens de handle kunt opslaan en herstellen.
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;
}
};
Deze methoden sluiten elkaar niet uit. Het kan voorkomen dat u zowel expliciet een model in de browser in de cache plaatst als een model van de harde schijf van een gebruiker gebruikt.
Demo
U kunt alle drie de reguliere casusopslagmethoden en de harde schijfmethode zien die is geïmplementeerd in de MediaPipe LLM-demo .
Bonus: download een groot bestand in stukjes
Als u een groot AI-model van internet moet downloaden, parallelliseert u de download in afzonderlijke delen en voegt u deze vervolgens weer samen op de client.
Hier is een helperfunctie die u in uw code kunt gebruiken. U hoeft alleen de url
door te geven. De chunkSize
(standaard: 5 MB), de maxParallelRequests
(standaard: 6), de progressCallback
functie (die rapporteert over de downloadedBytes
en de totale fileSize
) en het signal
voor een AbortSignal
signaal zijn allemaal optioneel.
U kunt de volgende functie in uw project kopiëren of het fetch-in-chunks
pakket installeren vanuit het npm- pakket.
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;
Kies de juiste methode voor u
In deze handleiding zijn verschillende methoden onderzocht voor het effectief cachen van AI-modellen in de browser, een taak die cruciaal is voor het verbeteren van de gebruikerservaring met en de prestaties van uw app. Het Chrome-opslagteam beveelt de Cache API aan voor optimale prestaties, om snelle toegang tot AI-modellen te garanderen, de laadtijden te verminderen en de responsiviteit te verbeteren.
De OPFS en IndexedDB zijn minder bruikbare opties. De OPFS- en de IndexedDB-API's moeten de gegevens serialiseren voordat deze kunnen worden opgeslagen. IndexedDB moet de gegevens ook deserialiseren wanneer deze worden opgehaald, waardoor dit de slechtste plaats is om grote modellen op te slaan.
Voor nichetoepassingen biedt de File System Access API directe toegang tot bestanden op het apparaat van een gebruiker, ideaal voor gebruikers die hun eigen AI-modellen beheren.
Als u uw AI-model wilt beveiligen, bewaar het dan op de server. Eenmaal opgeslagen op de client, is het triviaal om de gegevens uit zowel de cache als de IndexedDB te extraheren met DevTools of de OFPS DevTools-extensie . Deze opslag-API's zijn inherent gelijk wat betreft beveiliging. U komt misschien in de verleiding om een gecodeerde versie van het model op te slaan, maar u moet dan de decoderingssleutel bij de client krijgen, die kan worden onderschept. Dit betekent dat de poging van een slechte acteur om je model te stelen iets moeilijker is, maar niet onmogelijk.
We raden u aan een cachingstrategie te kiezen die aansluit bij de vereisten van uw app, het doelgroepgedrag en de kenmerken van de gebruikte AI-modellen. Dit zorgt ervoor dat uw applicaties responsief en robuust zijn onder verschillende netwerkomstandigheden en systeembeperkingen.
Dankbetuigingen
Dit werd beoordeeld door Joshua Bell, Reilly Grant, Evan Stade, Nathan Memmott, Austin Sullivan, Etienne Noël, André Bandarra, Alexandra Klepper, François Beaufort, Paul Kinlan en Rachel Andrew.