Pobieranie modeli AI za pomocą interfejsu Background Fetch API

Data publikacji: 20 lutego 2025 r.

Pobieranie dużych modeli AI w sposób niezawodny jest trudnym zadaniem. Jeśli użytkownicy stracą połączenie z internetem lub zamkną Twoją witrynę lub aplikację internetową, utracą częściowo pobrane pliki modeli i po powrocie na stronę będą musieli zacząć od nowa. Korzystając z Background Fetch API jako ulepszenia stopniowego, możesz znacznie poprawić wygodę użytkowników.

Browser Support

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

Source

Rejestrowanie skryptu service worker

Interfejs Background Fetch API wymaga, aby aplikacja zarejestrowała skrypt 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);
  });
}

Wywołanie pobrania w tle

Podczas pobierania przeglądarka wyświetla użytkownikowi postęp i metodę anulowania pobierania. Po zakończeniu pobierania przeglądarka uruchamia usługę, a aplikacja może podjąć działanie na podstawie otrzymanej odpowiedzi.

Interfejs Background Fetch API może nawet przygotować pobieranie do rozpoczęcia w trybie offline. Po ponownym połączeniu się z siecią pobieranie rozpocznie się natychmiast. Jeśli użytkownik przejdzie w tryb offline, proces zostanie wstrzymany do czasu, gdy ponownie połączy się z internetem.

W tym przykładzie użytkownik klika przycisk, aby pobrać Gemma 2B. Zanim pobieramy model, sprawdzamy, czy został on wcześniej pobrany i zapisany w pamięci podręcznej, aby nie używać niepotrzebnych zasobów. Jeśli nie ma go w pamięci podręcznej, rozpoczynamy pobieranie w tle.

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

Funkcja getResourceSize() zwraca rozmiar w bajtach pobranego pliku. Aby to zrobić, prześlij 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;
  }
};

Postęp pobierania raportu

Po rozpoczęciu pobierania w tle przeglądarka zwraca wartość BackgroundFetchRegistration. Możesz go użyć, aby poinformować użytkownika o postępie pobierania za pomocą zdarzenia 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}`);
});

Powiadamianie użytkowników i klienta o zakończeniu pobierania

Gdy pobranie w tle się powiedzie, worker usługi aplikacji otrzyma zdarzenie backgroundfetchsuccess.

Ten kod jest zawarty w usługach działających w tle. wywołanie updateUI() pod koniec pozwala zaktualizować interfejs przeglądarki, aby powiadomić użytkownika o udanym pobraniu w tle. Na koniec poinformuj klienta o zakończonym pobieraniu, na przykład za pomocą 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,
          });
        }
      });
    })(),
  );
});

Odbieranie wiadomości od usługi w tle

Aby otrzymać na kliencie wysłaną wiadomość o udanym zakończeniu pobierania, nasłuchuj zdarzenia message. Po otrzymaniu wiadomości od service workera możesz pracować z modelem AI i przechowywać go za pomocą interfejsu Cache API.

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

Anulowanie pobierania w tle

Aby umożliwić użytkownikowi anulowanie trwającego pobierania, użyj metody abort() interfejsu BackgroundFetchRegistration.

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

Zapisz model w pamięci podręcznej

Przechowuj pobrane modele, aby użytkownicy pobierali je tylko raz. Chociaż interfejs Background Fetch API poprawia komfort pobierania, zawsze staraj się używać jak najmniejszego modelu AI po stronie klienta.

Te interfejsy API pomogą Ci zapewnić użytkownikom lepsze wrażenia z korzystania z AI po stronie klienta.

Prezentacja

Pełne wdrożenie tego podejścia znajdziesz w demo i w kodzie źródłowym.

Panel aplikacji w Narzędziach deweloperskich Chrome otwarty na pobranie w tle.
W Chrome DevTools możesz wyświetlić podgląd zdarzeń związanych z trwającymi pobraniami w tle. Demonstracja pokazuje trwające pobieranie, w którym zostało pobranych 17,54 M megabajtów z łącznej liczby 1,26 gigabajta. Wskaźnik pobierania w przeglądarce pokazuje też trwające pobieranie.

Podziękowania

Ten przewodnik został sprawdzony przez Françoisa Beauforta, Andre Bandarrę, Sebastiana Benza, Maud Nalpas i Alexandrę Klepper.