Télécharger des modèles d'IA avec l'API Background Fetch

Publié le 20 février 2025

Télécharger de manière fiable de grands modèles d'IA est une tâche difficile. Si les utilisateurs perdent leur connexion Internet ou ferment votre site Web ou votre application Web, ils perdent les fichiers de modèle partiellement téléchargés et doivent recommencer à zéro lorsqu'ils reviennent sur votre page. En utilisant l'API Background Fetch comme amélioration progressive, vous pouvez améliorer considérablement l'expérience utilisateur.

Browser Support

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

Source

Enregistrer un service worker

L'API Background Fetch nécessite que votre application enregistre 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);
  });
}

Déclencher une récupération en arrière-plan

Lorsque le navigateur récupère le fichier, il affiche la progression à l'utilisateur et lui fournit une méthode pour annuler le téléchargement. Une fois le téléchargement terminé, le navigateur démarre le service worker et l'application peut prendre des mesures avec la réponse.

L'API Background Fetch peut même préparer le démarrage de la récupération hors connexion. Dès que l'utilisateur se reconnecte, le téléchargement commence. Si l'utilisateur se déconnecte, le processus est mis en pause jusqu'à ce qu'il soit de nouveau en ligne.

Dans l'exemple suivant, l'utilisateur clique sur un bouton pour télécharger Gemma 2B. Avant d'effectuer la récupération, nous vérifions si le modèle a déjà été téléchargé et mis en cache afin de ne pas utiliser de ressources inutiles. Si elle n'est pas mise en cache, nous démarrons la récupération en arrière-plan.

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 fonction getResourceSize() renvoie la taille en octets du téléchargement. Pour ce faire, envoyez une requête 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;
  }
};

Progression du téléchargement des rapports

Une fois la récupération en arrière-plan lancée, le navigateur renvoie un BackgroundFetchRegistration. Vous pouvez l'utiliser pour informer l'utilisateur de la progression du téléchargement, avec l'événement 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}`);
});

Informer les utilisateurs et le client de la fin de la récupération

Une fois la récupération en arrière-plan réussie, le service worker de votre application reçoit un événement backgroundfetchsuccess.

Le code suivant est inclus dans le service worker. L'appel updateUI() vers la fin vous permet de mettre à jour l'interface du navigateur pour informer l'utilisateur de la récupération en arrière-plan réussie. Enfin, informez le client du téléchargement terminé, par exemple à l'aide de 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,
          });
        }
      });
    })(),
  );
});

Recevoir des messages du service worker

Pour recevoir le message de réussite envoyé concernant le téléchargement terminé sur le client, écoutez les événements message. Une fois que vous avez reçu le message du service worker, vous pouvez travailler avec le modèle d'IA et le stocker avec 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);
  }
});

Annuler une récupération en arrière-plan

Pour permettre à l'utilisateur d'annuler un téléchargement en cours, utilisez la méthode abort() de BackgroundFetchRegistration.

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

Mettre en cache le modèle

Mettez en cache les modèles téléchargés afin que vos utilisateurs ne les téléchargent qu'une seule fois. Bien que l'API Background Fetch améliore l'expérience de téléchargement, vous devez toujours essayer d'utiliser le modèle le plus petit possible dans l'IA côté client.

Ensemble, ces API vous aident à créer une meilleure expérience d'IA côté client pour vos utilisateurs.

Démo

Vous pouvez voir une implémentation complète de cette approche dans la démonstration et son code source.

Panneau "Application" des outils pour les développeurs Chrome ouvert sur le téléchargement de la récupération en arrière-plan.
Avec les outils pour les développeurs Chrome, vous pouvez prévisualiser les événements liés à la récupération en arrière-plan en cours. La démonstration montre un téléchargement en cours qui a atteint 17,54 mégaoctets, pour un total de 1,26 gigaoctets. L'indicateur de téléchargement du navigateur affiche également le téléchargement en cours.

Remerciements

Ce guide a été examiné par François Beaufort, Andre Bandarra, Sebastian Benz, Maud Nalpas et Alexandra Klepper.