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

Publié le : 20 février 2025

Le téléchargement 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 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 exige 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

Pendant la récupération du navigateur, la progression est affichée à l'utilisateur, qui dispose d'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 agir en fonction de la réponse.

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

Dans l'exemple suivant, l'utilisateur clique sur un bouton pour télécharger Gemma 2B. Avant de récupérer le modèle, nous vérifions s'il 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 lançons 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 du rapport

Une fois l'extraction 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

Lorsque la récupération en arrière-plan réussit, 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éussite de la récupération en arrière-plan. Enfin, informez le client de la fin du téléchargement, 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 le modèle en cache

Mettez en cache les modèles téléchargés pour 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 plus petit modèle 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 de développement Chrome, vous pouvez prévisualiser les événements liés à la récupération en arrière-plan en cours. La démo montre un téléchargement en cours de 17,54 Mo sur un total de 1,26 Go. 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 Bandra, Sebastian Benz, Maud Nalpas et Alexandra Klepper.