Background Fetch API を使用して AI モデルをダウンロードする

公開日: 2025 年 2 月 20 日

大規模な AI モデルを確実にダウンロードすることは、困難なタスクです。ユーザーがインターネット接続を失ったり、ウェブサイトまたはウェブ アプリケーションを閉じたりすると、ダウンロードが完了していないモデルファイルが失われ、ページに戻ったときに最初からやり直す必要があります。Background Fetch API を段階的な拡張として使用することで、ユーザー エクスペリエンスを大幅に改善できます。

Browser Support

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

Source

Service Worker を登録する

Background Fetch API を使用するには、アプリでサービス ワーカーを登録する必要があります。

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

バックグラウンド フェッチをトリガーする

ブラウザがフェッチすると、進行状況がユーザーに表示され、ダウンロードをキャンセルする方法が提示されます。ダウンロードが完了すると、ブラウザはサービス ワーカーを開始し、アプリケーションはレスポンスでアクションを実行できます。

Background Fetch API では、オフラインで開始するように取得を準備することもできます。ユーザーが再接続するとすぐにダウンロードが開始されます。ユーザーがオフラインになると、ユーザーが再びオンラインになるまでプロセスは一時停止します。

次の例では、ユーザーがボタンをクリックして Gemma 2B をダウンロードします。フェッチする前に、モデルが以前にダウンロードされてキャッシュに保存されているかどうかを確認して、不要なリソースを使用しないようにします。キャッシュに保存されていない場合は、バックグラウンド フェッチを開始します。

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

getResourceSize() 関数は、ダウンロードのバイトサイズを返します。これは、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;
  }
};

レポートのダウンロードの進行状況

バックグラウンド フェッチが開始されると、ブラウザは BackgroundFetchRegistration を返します。これを使用して、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}`);
});

取得の完了をユーザーとクライアントに通知する

バックグラウンド フェッチが成功すると、アプリのサービス ワーカーは backgroundfetchsuccess イベントを受信します。

次のコードはサービス ワーカーに含まれています。末尾付近の updateUI() 呼び出しにより、ブラウザのインターフェースを更新して、バックグラウンド取得が成功したことをユーザーに通知できます。最後に、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,
          });
        }
      });
    })(),
  );
});

サービス ワーカーからメッセージを受信する

クライアントで完了したダウンロードに関する送信された成功メッセージを受信するには、message イベントをリッスンします。サービス ワーカーからメッセージを受け取ったら、AI モデルを操作して 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);
  }
});

バックグラウンド フェッチをキャンセルする

進行中のダウンロードをユーザーがキャンセルできるようにするには、BackgroundFetchRegistrationabort() メソッドを使用します。

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

モデルをキャッシュに保存する

ダウンロードしたモデルをキャッシュに保存して、ユーザーがモデルを 1 回だけダウンロードするようにします。Background Fetch API はダウンロード エクスペリエンスを向上させますが、クライアントサイド AI では常に可能な限り小さいモデルを使用するようにしてください。

これらの API を組み合わせることで、ユーザー向けのクライアントサイド AI エクスペリエンスを向上させることができます。

デモ

このアプローチの完全な実装については、デモとそのソースコードをご覧ください。

Chrome DevTools の [アプリケーション] パネルが開き、バックグラウンド フェッチのダウンロードが表示されている。
Chrome DevTools を使用すると、進行中のバックグラウンド フェッチに関連するイベントをプレビューできます。このデモでは、17.54 M メガバイトのダウンロードが完了し、合計 1.26 GB のダウンロードが進行中です。ブラウザのダウンロード インジケーターにも、進行中のダウンロードが表示されます。

謝辞

このガイドは、François BeaufortAndre BandarraSebastian BenzMaud NalpasAlexandra Klepper がレビューしました。