Publicado: 20 de febrero de 2025
Descargar de forma confiable modelos de IA grandes es una tarea difícil. Si los usuarios pierden la conexión a Internet o cierran tu sitio web o aplicación web, perderán los archivos del modelo descargados parcialmente y deberán comenzar de nuevo cuando regresen a tu página. Si usas la API de Background Fetch como una mejora progresiva, puedes mejorar significativamente la experiencia del usuario.
Cómo registrar un service worker
La API de Background Fetch requiere que tu app registre un trabajador de servicio.
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);
});
}
Cómo activar una recuperación en segundo plano
A medida que el navegador recupera el archivo, muestra el progreso al usuario y le brinda un método para cancelar la descarga. Una vez que se completa la descarga, el navegador inicia el service worker y la aplicación puede tomar medidas con la respuesta.
La API de Background Fetch incluso puede preparar la recuperación para que comience sin conexión. La descarga comenzará en cuanto el usuario vuelva a conectarse. Si el usuario se desconecta, el proceso se pausa hasta que vuelva a conectarse.
En el siguiente ejemplo, el usuario hace clic en un botón para descargar Gemma 2B. Antes de recuperar el modelo, verificamos si se descargó y almacenó en caché anteriormente para no usar recursos innecesarios. Si no se encuentra en la caché, iniciamos la recuperación en segundo plano.
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),
});
}
});
La función getResourceSize() devuelve el tamaño en bytes de la descarga. Para implementar esto, realiza una solicitud 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;
}
};
Progreso de la descarga del informe
Una vez que comienza la recuperación en segundo plano, el navegador devuelve un BackgroundFetchRegistration.
Puedes usarlo para informar al usuario sobre el progreso de la descarga con el evento 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}`);
});
Notifica a los usuarios y al cliente que se completó la recuperación
Cuando la recuperación en segundo plano se realiza correctamente, el trabajador de servicio de tu app recibe un evento backgroundfetchsuccess.
El siguiente código se incluye en el trabajador de servicio. La llamada a updateUI() cerca del final te permite actualizar la interfaz del navegador para notificar al usuario que la recuperación en segundo plano se realizó correctamente. Por último, informa al cliente sobre la descarga finalizada, por ejemplo, con 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,
});
}
});
})(),
);
});
Recibir mensajes del service worker
Para recibir el mensaje de éxito enviado sobre la descarga completada en el cliente, escucha los eventos message. Una vez que recibas el mensaje del trabajador de servicio, puedes trabajar con el modelo de IA y almacenarlo con la API de Cache.
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);
}
});
Cómo cancelar una recuperación en segundo plano
Para permitir que el usuario cancele una descarga en curso, usa el método abort() de BackgroundFetchRegistration.
const registration = await navigator.serviceWorker.ready;
const bgFetch = await registration.backgroundFetch.get(FETCH_ID);
if (!bgFetch) {
return;
}
await bgFetch.abort();
Almacena el modelo en caché
Almacena en caché los modelos descargados para que los usuarios solo descarguen el modelo una vez. Si bien la API de Background Fetch mejora la experiencia de descarga, siempre debes intentar usar el modelo más pequeño posible en la IA del cliente.
En conjunto, estas APIs te ayudan a crear una mejor experiencia de IA del cliente para tus usuarios.
Demostración
Puedes ver una implementación completa de este enfoque en la demostración y su código fuente.
Agradecimientos
François Beaufort, Andre Bandarra, Sebastian Benz, Maud Nalpas y Alexandra Klepper revisaron esta guía.