백그라운드 가져오기 API를 사용하여 AI 모델 다운로드

게시일: 2025년 2월 20일

대규모 AI 모델을 안정적으로 다운로드하는 것은 어려운 작업입니다. 사용자가 인터넷 연결이 끊기거나 웹사이트 또는 웹 애플리케이션을 닫으면 부분적으로 다운로드된 모델 파일이 손실되며 페이지로 돌아와 다시 시작해야 합니다. 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);
  });
}

백그라운드 가져오기 트리거

브라우저가 가져오면 사용자에게 진행률을 표시하고 다운로드를 취소하는 방법을 제공합니다. 다운로드가 완료되면 브라우저가 서비스 워커를 시작하고 애플리케이션은 응답으로 작업을 실행할 수 있습니다.

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

모델 캐시

사용자가 모델을 한 번만 다운로드하도록 다운로드된 모델을 캐시합니다. Background Fetch API는 다운로드 환경을 개선하지만 클라이언트 측 AI에서는 항상 가능한 한 작은 모델을 사용하는 것이 좋습니다.

이러한 API를 함께 사용하면 사용자에게 더 나은 클라이언트 측 AI 환경을 제공할 수 있습니다.

데모

이 접근 방식의 전체 구현은 데모소스 코드에서 확인할 수 있습니다.

백그라운드 가져오기 다운로드로 열려 있는 Chrome DevTools 애플리케이션 패널
Chrome DevTools를 사용하면 진행 중인 백그라운드 가져오기와 관련된 이벤트를 미리 볼 수 있습니다. 데모에서는 진행 중인 다운로드 중 17,540,000MB가 완료되었으며 총 1.26GB가 남아 있음을 보여줍니다. 브라우저의 다운로드 표시기에도 진행 중인 다운로드가 표시됩니다.

감사의 말씀

이 가이드는 프랑소와 보포르, 안드레 반다라, 세바스티안 벤츠, 모드 날파스, 알렉산드라 클레퍼가 검토했습니다.