הורדת מודלים של AI באמצעות Background Fetch API

תאריך פרסום: 20 בפברואר 2025

הורדה מהימנה של מודלים גדולים של 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);
  });
}

הפעלת אחזור ברקע

בזמן שהדפדפן מאחזר את הקובץ, הוא מציג למשתמש את ההתקדמות ומאפשר לו לבטל את ההורדה. בסיום ההורדה, הדפדפן מפעיל את ה-service worker והאפליקציה יכולה לבצע פעולה עם התגובה.

‏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 מתקבל ב-service worker של האפליקציה.

הקוד הבא נכלל ב-service worker. הקריאה 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,
          });
        }
      });
    })(),
  );
});

קבלת הודעות מה-service worker

כדי לקבל את הודעת ההצלחה על ההורדה שהושלמו בצד הלקוח, צריך להאזין לאירועים message. אחרי שתקבלו את ההודעה מה-service worker, תוכלו לעבוד עם מודל ה-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);
  }
});

ביטול אחזור ברקע

כדי לאפשר למשתמש לבטל הורדה מתמשכת, משתמשים בשיטה abort() של BackgroundFetchRegistration.

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

שמירת המודל במטמון

שמירת מודלים שהורדתם במטמון, כדי שהמשתמשים יורידו את המודל רק פעם אחת. ‏Background Fetch API משפרת את חוויית ההורדה, אבל תמיד כדאי להשתמש במודל הקטן ביותר האפשרי ב-AI בצד הלקוח.

יחד, ממשקי ה-API האלה עוזרים לכם ליצור חוויית AI טובה יותר בצד הלקוח למשתמשים שלכם.

הדגמה (דמו)

אפשר לראות הטמעה מלאה של הגישה הזו בדמו ובקוד המקור שלו.

חלונית האפליקציה של כלי הפיתוח ל-Chrome פתוחה להורדה של אחזור ברקע.
בעזרת הכלים למפתחים של Chrome, אפשר לראות תצוגה מקדימה של האירועים שקשורים לאחזור המתמשך ברקע. בהדגמה מוצגת הורדה מתמשכת שבוצעה בה עד כה 17.54 מיליון מגה-בייט, מתוך סך של 1.26 ג'יגה-בייט. גם באינדיקטור ההורדות בדפדפן מוצגת ההורדה המתמשכת.

תודות

הבדיקה של המדריך בוצעה על ידי François Beaufort,‏ Andre Bandarra,‏ Sebastian Benz,‏ Maud Nalpas ו-Alexandra Klepper.