تنزيل نماذج الذكاء الاصطناعي باستخدام Background Fetch API

تاريخ النشر: 20 شباط (فبراير) 2025

إنّ تنزيل نماذج الذكاء الاصطناعي الكبيرة بشكل موثوق به هو مهمة صعبة. إذا فقد المستخدمون الاتصال بالإنترنت أو أغلقوا موقعك الإلكتروني أو تطبيقك على الويب، سيفقدون ملفّات النماذج التي تم تنزيلها جزئيًا وسيضطرون إلى البدء من جديد عند العودة إلى صفحتك. من خلال استخدام واجهة برمجة التطبيقات Background Fetch API كأحد ميزات التحسين التدريجي، يمكنك تحسين تجربة المستخدم بشكل كبير.

Browser Support

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

Source

تسجيل مشغّل خدمات

تتطلّب 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);
  });
}

بدء عملية استرجاع في الخلفية

أثناء جلب المتصفّح للبيانات، يعرض مستوى التقدّم للمستخدم ويمنحه طريقة لإلغاء عملية التنزيل. بعد اكتمال التنزيل، يبدأ المتصفّح خدمة 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.

يتم تضمين الرمز البرمجي التالي في الخدمة العاملة. يتيح لك الإجراء 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. بعد تلقّي الرسالة من الخدمة العاملة، يمكنك استخدام نموذج الذكاء الاصطناعي وتخزينه باستخدام 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 تحسِّن تجربة التنزيل، يجب دائمًا استخدام أصغر نموذج ممكن في الذكاء الاصطناعي من جهة العميل.

وتساعدك واجهات برمجة التطبيقات هذه معًا في توفير تجربة أفضل لتكنولوجيات الذكاء الاصطناعي من جهة العميل لمستخدميك.

عرض توضيحي

يمكنك الاطّلاع على تنفيذ كامل لهذا النهج في الإصدار التجريبي و رمزه المصدر.

لوحة تطبيق "أدوات مطوّري البرامج في Chrome" مفتوحة لتنزيل ميزة "جلب البيانات في الخلفية"
باستخدام Chrome DevTools، يمكنك معاينة الأحداث ذات الصلة بالبحث المستمر في الخلفية. يعرض العرض التوضيحي عملية تنزيل جارية تم تنزيل 17.54 مليون ميغابايت منها، وبلغ إجمالي حجم الملف 1.26 غيغابايت. يعرض مؤشر التنزيل في المتصفّح أيضًا عملية التنزيل الجارية.

الشكر والتقدير

راجع هذا الدليل كلّ من François Beaufort و Andre Bandarra و Sebastian Benz و Maud Nalpas و Alexandra Klepper.