Tải mô hình AI xuống bằng Background Fetch API

Ngày xuất bản: 20 tháng 2 năm 2025

Việc tải các mô hình AI lớn xuống một cách đáng tin cậy là một nhiệm vụ đầy thách thức. Nếu người dùng mất kết nối Internet hoặc đóng trang web hoặc ứng dụng web của bạn, họ sẽ mất một phần tệp mô hình đã tải xuống và phải bắt đầu lại khi quay lại trang của bạn. Bằng cách sử dụng Background Fetch API (API Tìm nạp trong nền) làm tính năng cải tiến dần, bạn có thể cải thiện đáng kể trải nghiệm người dùng.

Browser Support

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

Source

Đăng ký trình chạy dịch vụ

API Tìm nạp ở chế độ nền yêu cầu ứng dụng của bạn đăng ký một worker dịch vụ.

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

Kích hoạt một lượt tìm nạp ở chế độ nền

Khi tìm nạp, trình duyệt sẽ hiển thị tiến trình cho người dùng và cung cấp cho họ một phương thức để huỷ tải xuống. Sau khi tải xuống xong, trình duyệt sẽ khởi động worker dịch vụ và ứng dụng có thể thực hiện hành động bằng phản hồi.

API Tìm nạp ở chế độ nền thậm chí có thể chuẩn bị để bắt đầu tìm nạp khi không có mạng. Ngay khi người dùng kết nối lại, quá trình tải xuống sẽ bắt đầu. Nếu người dùng rời khỏi mạng, quá trình này sẽ tạm dừng cho đến khi người dùng có mạng trở lại.

Trong ví dụ sau, người dùng nhấp vào một nút để tải Gemma 2B xuống. Trước khi tìm nạp, chúng ta kiểm tra xem mô hình đã được tải xuống và lưu vào bộ nhớ đệm trước đó hay chưa để không sử dụng các tài nguyên không cần thiết. Nếu không được lưu vào bộ nhớ đệm, chúng ta sẽ bắt đầu tìm nạp ở chế độ nền.

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

Hàm getResourceSize() trả về kích thước tệp tải xuống tính bằng byte. Bạn có thể triển khai việc này bằng cách tạo một yêu cầu 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;
  }
};

Báo cáo tiến trình tải xuống

Sau khi quá trình tìm nạp ở chế độ nền bắt đầu, trình duyệt sẽ trả về một BackgroundFetchRegistration. Bạn có thể sử dụng thông tin này để thông báo cho người dùng về tiến trình tải xuống bằng sự kiện 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}`);
});

Thông báo cho người dùng và ứng dụng khách về việc hoàn tất quá trình tìm nạp

Khi quá trình tìm nạp ở chế độ nền thành công, worker dịch vụ của ứng dụng sẽ nhận được sự kiện backgroundfetchsuccess.

Mã sau đây được đưa vào worker dịch vụ. Lệnh gọi updateUI() ở gần cuối cho phép bạn cập nhật giao diện của trình duyệt để thông báo cho người dùng về việc tìm nạp ở chế độ nền thành công. Cuối cùng, hãy thông báo cho ứng dụng về việc tải xuống đã hoàn tất, chẳng hạn như bằng cách sử dụng 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,
          });
        }
      });
    })(),
  );
});

Nhận thông báo từ worker dịch vụ

Để nhận thông báo thành công về việc tải xuống đã hoàn tất trên ứng dụng, hãy theo dõi các sự kiện message. Sau khi nhận được thông báo từ worker dịch vụ, bạn có thể làm việc với mô hình AI và lưu trữ mô hình đó bằng API bộ nhớ đệm.

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

Huỷ tìm nạp trong nền

Để cho phép người dùng huỷ một tệp đang tải xuống, hãy sử dụng phương thức abort() của BackgroundFetchRegistration.

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

Lưu mô hình vào bộ nhớ đệm

Lưu mô hình đã tải xuống vào bộ nhớ đệm để người dùng chỉ tải mô hình xuống một lần. Mặc dù API Tìm nạp ở chế độ nền giúp cải thiện trải nghiệm tải xuống, nhưng bạn phải luôn hướng đến việc sử dụng mô hình nhỏ nhất có thể trong AI phía máy khách.

Các API này cùng nhau giúp bạn tạo ra trải nghiệm AI tốt hơn cho người dùng ở phía máy khách.

Bản minh hoạ

Bạn có thể xem cách triển khai đầy đủ phương pháp này trong bản minh hoạmã nguồn của phương pháp này.

Bảng điều khiển Ứng dụng của Công cụ của Chrome cho nhà phát triển mở ra để tải xuống tính năng Tìm nạp ở chế độ nền.
Bằng Công cụ của Chrome cho nhà phát triển, bạn có thể xem trước các sự kiện liên quan đến quá trình tìm nạp trong nền đang diễn ra. Bản minh hoạ cho thấy một quá trình tải xuống đang diễn ra với 17,54 megabyte đã tải xuống, tổng cộng 1,26 gigabyte. Chỉ báo Tải xuống của trình duyệt cũng cho thấy quá trình tải xuống đang diễn ra.

Lời cảm ơn

Hướng dẫn này đã được François Beaufort, Andre Bandarra, Sebastian Benz, Maud NalpasAlexandra Klepper xem xét.