Scaricare modelli di IA con l'API Background Fetch

Data di pubblicazione: 20 febbraio 2025

Scaricare in modo affidabile modelli di AI di grandi dimensioni è un'attività impegnativa. Se gli utenti perdono la connessione a internet o chiudono il tuo sito web o la tua applicazione web, perdono i file del modello scaricati parzialmente e devono ricominciare quando tornano alla tua pagina. Utilizzando l'API Background Fetch come miglioramento progressivo, puoi migliorare significativamente l'esperienza utente.

Browser Support

  • Chrome: 74.
  • Edge: 79.
  • Firefox: not supported.
  • Safari: not supported.

Source

Registrare un service worker

L'API Background Fetch richiede che la tua app registri un service worker.

if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    const registration = await navigator.serviceWorker.register('sw.js');
    console.log('Service worker registered for scope', registration.scope);
  });
}

Attivare un recupero dello sfondo

Man mano che il browser recupera i dati, mostra all'utente lo stato di avanzamento e gli offre un metodo per annullare il download. Al termine del download, il browser avvia il service worker e l'applicazione può intervenire con la risposta.

L'API Background Fetch può persino preparare il recupero per l'avvio in modalità offline. Non appena l'utente si riconnette, il download inizia. Se l'utente va offline, la procedura si interrompe finché l'utente non torna online.

Nell'esempio seguente, l'utente fa clic su un pulsante per scaricare Gemma 2B. Prima di recuperare, controlliamo se il modello è stato scaricato e memorizzato nella cache in precedenza, in modo da non utilizzare risorse non necessarie. Se non è memorizzato nella cache, avviamo il recupero in background.

const FETCH_ID = 'gemma-2b';
const MODEL_URL =
  'https://storage.googleapis.com/jmstore/kaggleweb/grader/g-2b-it-gpu-int4.bin';

downloadButton.addEventListener('click', async (event) => {
  // If the model is already downloaded, return it from the cache.
  const modelAlreadyDownloaded = await caches.match(MODEL_URL);
  if (modelAlreadyDownloaded) {
    const modelBlob = await modelAlreadyDownloaded.blob();
    // Do something with the model.
    console.log(modelBlob);
    return;
  }

  // The model still needs to be downloaded.
  // Feature detection and fallback to classic `fetch()`.
  if (!('BackgroundFetchManager' in self)) {
    try {
      const response = await fetch(MODEL_URL);
      if (!response.ok || response.status !== 200) {
        throw new Error(`Download failed ${MODEL_URL}`);
      }
      const modelBlob = await response.blob();
      // Do something with the model.
      console.log(modelBlob);
      return;
    } catch (err) {
      console.error(err);
    }
  }

  // The service worker registration.
  const registration = await navigator.serviceWorker.ready;

  // Check if there's already a background fetch running for the `FETCH_ID`.
  let bgFetch = await registration.backgroundFetch.get(FETCH_ID);

  // If not, start a background fetch.
  if (!bgFetch) {
    bgFetch = await registration.backgroundFetch.fetch(FETCH_ID, MODEL_URL, {
      title: 'Gemma 2B model',
      icons: [
        {
          src: 'icon.png',
          size: '128x128',
          type: 'image/png',
        },
      ],
      downloadTotal: await getResourceSize(MODEL_URL),
    });
  }
});

La funzione getResourceSize() restituisce le dimensioni in byte del download. Puoi implementare questa operazione effettuando una richiesta HEAD.

const getResourceSize = async (url) => {
  try {
    const response = await fetch(url, { method: 'HEAD' });
    if (response.ok) {
      return response.headers.get('Content-Length');
    }
    console.error(`HTTP error: ${response.status}`);
    return 0;
  } catch (error) {
    console.error('Error fetching content size:', error);
    return 0;
  }
};

Avanzamento del download del report

Una volta avviato il recupero in background, il browser restituisce un BackgroundFetchRegistration. Puoi utilizzare questo evento per informare l'utente sullo stato di avanzamento del download.progress

bgFetch.addEventListener('progress', (e) => {
  // There's no download progress yet.
  if (!bgFetch.downloadTotal) {
    return;
  }
  // Something went wrong.
  if (bgFetch.failureReason) {
    console.error(bgFetch.failureReason);
  }
  if (bgFetch.result === 'success') {
    return;
  }
  // Update the user about progress.
  console.log(`${bgFetch.downloaded} / ${bgFetch.downloadTotal}`);
});

Notifica agli utenti e al cliente il completamento del recupero

Quando il recupero in background va a buon fine, il service worker dell'app riceve un evento backgroundfetchsuccess.

Il seguente codice è incluso nel service worker. La chiamata updateUI() quasi alla fine ti consente di aggiornare l'interfaccia del browser per comunicare all'utente il recupero in background riuscito. Infine, informa il client del download completato, ad esempio utilizzando postMessage().

self.addEventListener('backgroundfetchsuccess', (event) => {
  // Get the background fetch registration.
  const bgFetch = event.registration;

  event.waitUntil(
    (async () => {
      // Open a cache named 'downloads'.
      const cache = await caches.open('downloads');
      // Go over all records in the background fetch registration.
      // (In the running example, there's just one record, but this way
      // the code is future-proof.)
      const records = await bgFetch.matchAll();
      // Wait for the response(s) to be ready, then cache it/them.
      const promises = records.map(async (record) => {
        const response = await record.responseReady;
        await cache.put(record.request, response);
      });
      await Promise.all(promises);

      // Update the browser UI.
      event.updateUI({ title: 'Model downloaded' });

      // Inform the clients that the model was downloaded.
      self.clients.matchAll().then((clientList) => {
        for (const client of clientList) {
          client.postMessage({
            message: 'download-complete',
            id: bgFetch.id,
          });
        }
      });
    })(),
  );
});

Ricevere messaggi dal service worker

Per ricevere il messaggio di conferma dell'avvenuto download sul client, ascolta gli eventi message. Una volta ricevuto il messaggio dal service worker, puoi lavorare con il modello di AI e memorizzarlo con l'API Cache.

navigator.serviceWorker.addEventListener('message', async (event) => {
  const cache = await caches.open('downloads');
  const keys = await cache.keys();
  for (const key of keys) {
    const modelBlob = await cache
      .match(key)
      .then((response) => response.blob());
    // Do something with the model.
    console.log(modelBlob);
  }
});

Annullare un recupero in background

Per consentire all'utente di annullare un download in corso, utilizza il metodo abort() di BackgroundFetchRegistration.

const registration = await navigator.serviceWorker.ready;
const bgFetch = await registration.backgroundFetch.get(FETCH_ID);
if (!bgFetch) {
  return;
}
await bgFetch.abort();

Memorizzare nella cache il modello

Memorizza nella cache i modelli scaricati, in modo che gli utenti scarichino il modello una sola volta. Sebbene l'API Background Fetch migliori l'esperienza di download, dovresti sempre cercare di utilizzare il modello più piccolo possibile nell'AI lato client.

Insieme, queste API ti aiutano a creare una migliore esperienza di AI lato client per i tuoi utenti.

Demo

Puoi vedere un'implementazione completa di questo approccio nella demo e nel relativo codice sorgente.

Il riquadro Applicazione di Chrome DevTools aperto al download di Background Fetch.
Con Chrome DevTools, puoi visualizzare l'anteprima degli eventi relativi al recupero in background in corso. La demo mostra un download in corso di 17,54 MB su un totale di 1,26 GB. L'indicatore di download del browser mostra anche il download in corso.

Ringraziamenti

Questa guida è stata esaminata da François Beaufort, Andre Bandarra, Sebastian Benz, Maud Nalpas e Alexandra Klepper.