KI-Modelle mit der Background Fetch API herunterladen

Veröffentlicht: 20. Februar 2025

Das zuverlässige Herunterladen großer KI‑Modelle ist eine schwierige Aufgabe. Wenn Nutzer die Internetverbindung verlieren oder Ihre Website oder Webanwendung schließen, gehen teilweise heruntergeladene Modelldateien verloren und sie müssen von vorn beginnen, wenn sie auf Ihre Seite zurückkehren. Wenn Sie die Background Fetch API als progressive Verbesserung verwenden, können Sie die Nutzerfreundlichkeit erheblich verbessern.

Browser Support

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

Source

Service Worker registrieren

Für die Background Fetch API muss Ihre App einen Service Worker registrieren.

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);
  });
}

Hintergrundabruf auslösen

Während der Browser die Daten abruft, wird dem Nutzer der Fortschritt angezeigt und er hat die Möglichkeit, den Download abzubrechen. Sobald der Download abgeschlossen ist, startet der Browser den Service Worker und die Anwendung kann auf die Antwort reagieren.

Mit der Background Fetch API kann der Abruf sogar vorbereitet werden, während das Gerät offline ist. Sobald der Nutzer die Verbindung wiederherstellt, beginnt der Download. Wenn der Nutzer offline geht, wird der Vorgang pausiert, bis er wieder online ist.

Im folgenden Beispiel klickt der Nutzer auf eine Schaltfläche, um Gemma 2B herunterzuladen. Bevor wir das Modell abrufen, prüfen wir, ob es bereits heruntergeladen und im Cache gespeichert wurde, um unnötige Ressourcen zu vermeiden. Wenn sie nicht im Cache gespeichert ist, starten wir den Hintergrundabruf.

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),
    });
  }
});

Die Funktion getResourceSize() gibt die Größe des Downloads in Byte zurück. Sie können dies implementieren, indem Sie eine HEAD-Anfrage senden.

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;
  }
};

Downloadfortschritt für Berichte

Sobald der Hintergrundabruf beginnt, gibt der Browser ein BackgroundFetchRegistration zurück. Mit dem Ereignis progress können Sie den Nutzer über den Downloadfortschritt informieren.

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}`);
});

Nutzer und Client über den Abschluss des Abrufs benachrichtigen

Wenn der Hintergrundabruf erfolgreich ist, empfängt der Service Worker Ihrer App ein backgroundfetchsuccess-Ereignis.

Der folgende Code ist im Service Worker enthalten. Mit dem Aufruf updateUI() am Ende können Sie die Browserschnittstelle aktualisieren, um den Nutzer über den erfolgreichen Hintergrundabruf zu informieren. Informieren Sie den Client schließlich über den abgeschlossenen Download, z. B. mit 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,
          });
        }
      });
    })(),
  );
});

Nachrichten vom Service Worker empfangen

Damit der Client die gesendete Erfolgsmeldung zum abgeschlossenen Download empfängt, müssen Sie auf message-Ereignisse warten. Sobald Sie die Nachricht vom Service Worker erhalten haben, können Sie mit dem KI-Modell arbeiten und es mit der Cache API speichern.

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);
  }
});

Abrufen im Hintergrund abbrechen

Wenn der Nutzer einen laufenden Download abbrechen soll, verwenden Sie die Methode abort() der BackgroundFetchRegistration.

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

Modell im Cache speichern

Heruntergeladene Modelle im Cache speichern, damit Nutzer das Modell nur einmal herunterladen müssen. Die Background Fetch API verbessert zwar das Herunterladen, Sie sollten aber immer versuchen, das kleinstmögliche Modell für clientseitige KI zu verwenden.

Mit diesen APIs können Sie eine bessere clientseitige KI-Erfahrung für Ihre Nutzer schaffen.

Demo

Eine vollständige Implementierung dieses Ansatzes finden Sie in der Demo und im Quellcode.

Der Bereich „Anwendung“ in den Chrome-Entwicklertools ist geöffnet und zeigt den Download von Background Fetch.
Mit Chrome-Entwicklertools können Sie eine Vorschau der Ereignisse im Zusammenhang mit dem laufenden Hintergrundabruf ansehen. In der Demo ist ein laufender Download zu sehen, bei dem 17,54 MB von insgesamt 1,26 GB heruntergeladen wurden. Die laufenden Downloads werden auch in der Downloadanzeige des Browsers angezeigt.

Danksagungen

Dieser Leitfaden wurde von François Beaufort, Andre Bandarra, Sebastian Benz, Maud Nalpas und Alexandra Klepper geprüft.