Mendownload model AI dengan Background Fetch API

Dipublikasikan: 20 Februari 2025

Mendownload model AI besar dengan andal adalah tugas yang menantang. Jika pengguna kehilangan koneksi internet atau menutup situs atau aplikasi web Anda, mereka akan kehilangan file model yang didownload sebagian dan harus memulai lagi saat kembali ke halaman Anda. Dengan menggunakan Background Fetch API sebagai progressive enhancement, Anda dapat meningkatkan pengalaman pengguna secara signifikan.

Browser Support

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

Source

Mendaftarkan pekerja layanan

Background Fetch API mengharuskan aplikasi Anda mendaftarkan pekerja layanan.

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

Memicu pengambilan latar belakang

Saat mengambil, browser akan menampilkan progres kepada pengguna dan memberi mereka metode untuk membatalkan download. Setelah download selesai, browser akan memulai pekerja layanan dan aplikasi dapat mengambil tindakan dengan respons.

Background Fetch API bahkan dapat menyiapkan pengambilan untuk dimulai saat offline. Segera setelah pengguna terhubung kembali, download akan dimulai. Jika pengguna offline, proses akan dijeda hingga pengguna kembali online.

Pada contoh berikut, pengguna mengklik tombol untuk mendownload Gemma 2B. Sebelum mengambil, kita akan memeriksa apakah model sebelumnya telah didownload dan di-cache, sehingga kita tidak menggunakan resource yang tidak diperlukan. Jika tidak di-cache, kita akan memulai pengambilan latar belakang.

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

Fungsi getResourceSize() menampilkan ukuran byte download. Anda dapat menerapkan ini dengan membuat permintaan 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;
  }
};

Melaporkan progres download

Setelah pengambilan latar belakang dimulai, browser akan menampilkan BackgroundFetchRegistration. Anda dapat menggunakannya untuk memberi tahu pengguna tentang progres download, dengan peristiwa 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}`);
});

Memberi tahu pengguna dan klien tentang penyelesaian pengambilan

Saat pengambilan latar belakang berhasil, pekerja layanan aplikasi Anda akan menerima peristiwa backgroundfetchsuccess.

Kode berikut disertakan dalam pekerja layanan. Panggilan updateUI() di dekat akhir memungkinkan Anda memperbarui antarmuka browser untuk memberi tahu pengguna tentang pengambilan latar belakang yang berhasil. Terakhir, beri tahu klien tentang download yang telah selesai, misalnya, menggunakan 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,
          });
        }
      });
    })(),
  );
});

Menerima pesan dari pekerja layanan

Untuk menerima pesan sukses yang dikirim tentang download yang telah selesai di klien, proses peristiwa message. Setelah menerima pesan dari pekerja layanan, Anda dapat menggunakan model AI dan menyimpannya dengan 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);
  }
});

Membatalkan pengambilan latar belakang

Agar pengguna dapat membatalkan download yang sedang berlangsung, gunakan metode abort() dari BackgroundFetchRegistration.

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

Menyimpan model dalam cache

Menyimpan model yang didownload dalam cache, sehingga pengguna Anda hanya mendownload model sekali. Meskipun Background Fetch API meningkatkan pengalaman download, Anda harus selalu berusaha menggunakan model sekecil mungkin di AI sisi klien.

Bersama-sama, API ini membantu Anda menciptakan pengalaman AI sisi klien yang lebih baik bagi pengguna.

Demo

Anda dapat melihat implementasi lengkap pendekatan ini dalam demo dan kode sumbernya.

Panel Aplikasi Chrome DevTools terbuka untuk download Pengambilan Latar Belakang.
Dengan Chrome DevTools, Anda dapat melihat pratinjau peristiwa yang terkait dengan pengambilan latar belakang yang sedang berlangsung. Demo ini menampilkan download yang sedang berlangsung dengan total 17,54 megabyte yang telah selesai, dengan total 1,26 gigabyte. Indikator Download browser juga menampilkan download yang sedang berlangsung.

Ucapan terima kasih

Panduan ini ditinjau oleh François Beaufort, Andre Bandarra, Sebastian Benz, Maud Nalpas, dan Alexandra Klepper.