Tarayıcıda AI modellerini önbelleğe al

Çoğu yapay zeka modelinin en az bir ortak noktası vardır: Bunlar internet üzerinden aktarılan bir kaynak için oldukça büyüktür. En küçük MediaPipe nesne algılama modelinin (SSD MobileNetV2 float16) ağırlığı 5,6 MB, en büyüğünün ağırlığı ise 25 MB'tır.

gemma-2b-it-gpu-int4.bin adlı açık kaynak LLM'nin saati 1,35 GB'tır.Bu boyut, LLM için çok küçük olarak kabul edilir. Üretken yapay zeka modelleri çok büyük önem taşıyor. Günümüzde yapay zeka kullanımının çoğunlukla bulutta gerçekleşmesinin nedeni budur. Uygulamalar giderek doğrudan cihaz üzerinde son derece optimize edilmiş modeller çalıştırıyor. Tarayıcıda çalışan LLM'lerin demoları mevcut olsa da, aşağıda çalışan diğer modellere ilişkin bazı üretim düzeyinde örnekler verilmiştir:

İki zürafa ve ay olmak üzere üç nesne seçili halde, yapay zeka destekli nesne seçim aracı açık olarak web'de Adobe Photoshop.

Uygulamalarınızın gelecekte daha hızlı kullanıma sunulmasını sağlamak için örtülü HTTP tarayıcı önbelleğine güvenmek yerine, model verilerini cihaz üzerinde açık bir şekilde önbelleğe almalısınız.

Bu kılavuzda chatbot oluşturmak için gemma-2b-it-gpu-int4.bin model kullanılsa da bu yaklaşım, diğer modellere ve cihazdaki diğer kullanım alanlarına uyacak şekilde genelleştirilebilir. Bir uygulamayı bir modele bağlamanın en yaygın yolu, modeli geri kalan uygulama kaynaklarıyla birlikte sunmaktır. Yayını optimize etmek kritik önemdedir.

Doğru önbellek üstbilgilerini yapılandırma

Sunucunuzdan yapay zeka modelleri sunuyorsanız doğru Cache-Control üstbilgisinin yapılandırılması önemlidir. Aşağıdaki örnekte, uygulamanızın ihtiyaçlarına göre geliştirebileceğiniz sağlam bir varsayılan ayar gösterilmektedir.

Cache-Control: public, max-age=31536000, immutable

Bir AI modelinin yayınlanan her sürümü statik bir kaynaktır. Hiç değişmeyen içeriğe, istek URL'sinde önbellek bozma ile birlikte uzun bir max-age değeri verilmelidir. Modeli güncellemeniz gerekiyorsa yeni bir URL vermeniz gerekir.

Kullanıcı sayfayı yeniden yüklediğinde, sunucu içeriğin kararlı olduğunu bilse bile istemci bir yeniden doğrulama isteği gönderir. immutable yönergesinde, içerik değişmeyeceğinden yeniden doğrulamanın gerekli olmadığı açıkça belirtilir. immutable yönergesi, tarayıcılar ve ara önbellek veya proxy sunucuları tarafından geniş ölçüde desteklenmemektedir. Ancak bunu, evrensel olarak anlaşılan max-age yönergesiyle birleştirdiğinizde maksimum uyumluluk sağlayabilirsiniz. public yanıt yönergesi, yanıtın paylaşılan bir önbellekte depolanabileceğini belirtir.

Chrome Geliştirici Araçları, yapay zeka modeli isteğinde bulunurken Hugging Face tarafından gönderilen Cache-Control üretim başlıklarını gösterir. (Kaynak)

İstemci tarafında AI modellerini önbelleğe al

Bir yapay zeka modeli sunarken modeli tarayıcıda açık bir şekilde önbelleğe almanız önemlidir. Böylece, kullanıcı uygulamayı yeniden yükledikten sonra model verileri kullanılabilir hale gelir.

Bunu başarmak için kullanabileceğiniz birkaç teknik vardır. Aşağıdaki kod örnekleri için her model dosyasının bellekte blob adlı bir Blob nesnesinde depolandığını varsayın.

Performansı anlamak için her kod örneğine performance.mark() ve performance.measure() yöntemleriyle ek açıklama eklenir. Bu ölçümler cihaza bağlı olup genelleştirilemez.

Chrome Geliştirici Araçları Uygulama > Depolama bölümünde IndexedDB, Önbellek depolama alanı ve Dosya Sistemi segmentlerinin yer aldığı kullanım şemasını inceleyin. Her segmentin 1.354 megabayt veri kullandığı gösterilir. Bu da toplamda 4.063 megabayta karşılık gelir.

Tarayıcıda AI modellerini önbelleğe almak için şu API'lerden birini kullanmayı seçebilirsiniz: Cache API, Origin Private File System API ve IndexedDB API. Genel öneri, Cache API'nin kullanılmasıdır, ancak bu kılavuzda tüm seçeneklerin avantajları ve dezavantajları ele alınmaktadır.

Önbellek API'si

Cache API, uzun ömürlü bellekte önbelleğe alınan Request ve Response nesne çiftleri için kalıcı depolama alanı sağlar. Hizmet Çalışanları spesifikasyonunda tanımlanmış olsa da bu API'yi ana iş parçacığından veya normal bir çalışandan kullanabilirsiniz. Hizmet çalışanı bağlamı dışında kullanmak için Request nesnesi yerine sentetik URL ile eşlenmiş sentetik Response nesnesiyle Cache.put() yöntemini çağırın.

Bu kılavuzda, bellek içi blob olduğu varsayılmıştır. Önbellek anahtarı olarak sahte bir URL ve blob temelinde sentetik bir Response kullanın. Modeli doğrudan indirirseniz fetch() isteği göndererek elde edeceğiniz Response değerini kullanırsınız.

Örneğin, bir model dosyasının Cache API ile nasıl depolanacağı ve geri yükleneceği aşağıda açıklanmıştır.

const storeFileInSWCache = async (blob) => {
  try {
    performance.mark('start-sw-cache-cache');
    const modelCache = await caches.open('models');
    await modelCache.put('model.bin', new Response(blob));
    performance.mark('end-sw-cache-cache');

    const mark = performance.measure(
      'sw-cache-cache',
      'start-sw-cache-cache',
      'end-sw-cache-cache'
    );
    console.log('Model file cached in sw-cache.', mark.name, mark.duration.toFixed(2));
  } catch (err) {
    console.error(err.name, err.message);
  }
};

const restoreFileFromSWCache = async () => {
  try {
    performance.mark('start-sw-cache-restore');
    const modelCache = await caches.open('models');
    const response = await modelCache.match('model.bin');
    if (!response) {
      throw new Error(`File model.bin not found in sw-cache.`);
    }
    const file = await response.blob();
    performance.mark('end-sw-cache-restore');
    const mark = performance.measure(
      'sw-cache-restore',
      'start-sw-cache-restore',
      'end-sw-cache-restore'
    );
    console.log(mark.name, mark.duration.toFixed(2));
    console.log('Cached model file found in sw-cache.');
    return file;
  } catch (err) {    
    throw err;
  }
};

Kaynak Gizli Dosya Sistemi API'sı

Kaynak Gizli Dosya Sistemi (OPFS), depolama uç noktası için nispeten yeni bir standarttır. Bu, sayfanın kaynağına özeldir ve normal dosya sisteminin aksine kullanıcı tarafından görülmez. Performans açısından son derece optimize edilmiş özel bir dosyaya erişim sağlar ve içeriğine yazma erişimi sunar.

Örneğin, bir model dosyasının OPFS'de nasıl depolanacağı ve geri yükleneceği aşağıda açıklanmıştır.

const storeFileInOPFS = async (blob) => {
  try {
    performance.mark('start-opfs-cache');
    const root = await navigator.storage.getDirectory();
    const handle = await root.getFileHandle('model.bin', { create: true });
    const writable = await handle.createWritable();
    await blob.stream().pipeTo(writable);
    performance.mark('end-opfs-cache');
    const mark = performance.measure(
      'opfs-cache',
      'start-opfs-cache',
      'end-opfs-cache'
    );
    console.log('Model file cached in OPFS.', mark.name, mark.duration.toFixed(2));
  } catch (err) {
    console.error(err.name, err.message);
  }
};

const restoreFileFromOPFS = async () => {
  try {
    performance.mark('start-opfs-restore');
    const root = await navigator.storage.getDirectory();
    const handle = await root.getFileHandle('model.bin');
    const file = await handle.getFile();
    performance.mark('end-opfs-restore');
    const mark = performance.measure(
      'opfs-restore',
      'start-opfs-restore',
      'end-opfs-restore'
    );
    console.log('Cached model file found in OPFS.', mark.name, mark.duration.toFixed(2));
    return file;
  } catch (err) {    
    throw err;
  }
};

IndexedDB API'sı

IndexedDB, rastgele verilerin tarayıcıda kalıcı şekilde depolanması için iyi bilinen bir standarttır. Karmaşık API'siyle bilinse de idb-keyval gibi bir sarmalayıcı kitaplığı kullanarak IndexedDB'yi klasik bir anahtar/değer deposu gibi kullanabilirsiniz.

Örneğin:

import { get, set } from 'https://cdn.jsdelivr.net/npm/idb-keyval@latest/+esm';

const storeFileInIDB = async (blob) => {
  try {
    performance.mark('start-idb-cache');
    await set('model.bin', blob);
    performance.mark('end-idb-cache');
    const mark = performance.measure(
      'idb-cache',
      'start-idb-cache',
      'end-idb-cache'
    );
    console.log('Model file cached in IDB.', mark.name, mark.duration.toFixed(2));
  } catch (err) {
    console.error(err.name, err.message);
  }
};

const restoreFileFromIDB = async () => {
  try {
    performance.mark('start-idb-restore');
    const file = await get('model.bin');
    if (!file) {
      throw new Error('File model.bin not found in IDB.');
    }
    performance.mark('end-idb-restore');
    const mark = performance.measure(
      'idb-restore',
      'start-idb-restore',
      'end-idb-restore'
    );
    console.log('Cached model file found in IDB.', mark.name, mark.duration.toFixed(2));
    return file;
  } catch (err) {    
    throw err;
  }
};

Depolama alanını kalıcı olarak işaretle

Kalıcı depolama alanını kullanma izni istemek için bu önbelleğe alma yöntemlerinden herhangi birinin sonunda navigator.storage.persist() çağrısı yapın. Bu yöntem, izin verilirse true, aksi takdirde false olarak çözümlenen bir taahhüt döndürür. Tarayıcı, tarayıcıya özgü kurallara bağlı olarak isteği yerine getirebilir veya etmeyebilir.

if ('storage' in navigator && 'persist' in navigator.storage) {
  try {
    const persistent = await navigator.storage.persist();
    if (persistent) {
      console.log("Storage will not be cleared except by explicit user action.");
      return;
    }
    console.log("Storage may be cleared under storage pressure.");  
  } catch (err) {
    console.error(err.name, err.message);
  }
}

Özel durum: Sabit diskte model kullanma

Tarayıcı depolama alanına alternatif olarak AI modellerine doğrudan kullanıcının sabit diskinden referans verebilirsiniz. Bu teknik, araştırma odaklı uygulamaların belirli modelleri tarayıcıda çalıştırmanın uygulanabilirliğini sergilemesine veya sanatçıların uzman yaratıcılık uygulamalarında kendi kendine eğitilen modelleri kullanmalarına yardımcı olabilir.

File System Access API

File System Access API ile sabit diskteki dosyaları açabilir ve IndexedDB'ye devam edebileceğiniz bir FileSystemFileHandle elde edebilirsiniz.

Bu kalıpta, kullanıcının model dosyasına yalnızca bir kez erişim izni vermesi gerekir. Kalıcı izinler sayesinde kullanıcı, dosyaya kalıcı olarak erişim izni verebilir. Uygulama yeniden yüklendikten ve fare tıklaması gibi gerekli bir kullanıcı hareketinin ardından FileSystemFileHandle, sabit diskteki dosyaya erişim sağlayarak IndexedDB'den geri yüklenebilir.

Dosya erişim izinleri sorgulanır ve gerekirse istenir. Bu sayede, gelecekteki yeniden yükleme işlemleri için bu sorunsuz bir süreç sunulur. Aşağıdaki örnekte, sabit diskten bir dosya için herkese açık kullanıcı adının nasıl alınacağı ve ardından bu tutma yerinin nasıl depolanıp geri yükleneceği gösterilmektedir.

import { fileOpen } from 'https://cdn.jsdelivr.net/npm/browser-fs-access@latest/dist/index.modern.js';
import { get, set } from 'https://cdn.jsdelivr.net/npm/idb-keyval@latest/+esm';

button.addEventListener('click', async () => {
  try {
    const file = await fileOpen({
      extensions: ['.bin'],
      mimeTypes: ['application/octet-stream'],
      description: 'AI model files',
    });
    if (file.handle) {
      // It's an asynchronous method, but no need to await it.
      storeFileHandleInIDB(file.handle);
    }
    return file;
  } catch (err) {
    if (err.name !== 'AbortError') {
      console.error(err.name, err.message);
    }
  }
});

const storeFileHandleInIDB = async (handle) => {
  try {
    performance.mark('start-file-handle-cache');
    await set('model.bin.handle', handle);
    performance.mark('end-file-handle-cache');
    const mark = performance.measure(
      'file-handle-cache',
      'start-file-handle-cache',
      'end-file-handle-cache'
    );
    console.log('Model file handle cached in IDB.', mark.name, mark.duration.toFixed(2));
  } catch (err) {
    console.error(err.name, err.message);
  }
};

const restoreFileFromFileHandle = async () => {
  try {
    performance.mark('start-file-handle-restore');
    const handle = await get('model.bin.handle');
    if (!handle) {
      throw new Error('File handle model.bin.handle not found in IDB.');
    }
    if ((await handle.queryPermission()) !== 'granted') {
      const decision = await handle.requestPermission();
      if (decision === 'denied' || decision === 'prompt') {
        throw new Error(Access to file model.bin.handle not granted.');
      }
    }
    const file = await handle.getFile();
    performance.mark('end-file-handle-restore');
    const mark = performance.measure(
      'file-handle-restore',
      'start-file-handle-restore',
      'end-file-handle-restore'
    );
    console.log('Cached model file handle found in IDB.', mark.name, mark.duration.toFixed(2));
    return file;
  } catch (err) {    
    throw err;
  }
};

Bu yöntemler birlikte kullanılabilir. İkiniz de tarayıcıdaki bir modeli açık bir şekilde önbelleğe aldığınız ve kullanıcının sabit diskindeki bir modeli kullandığınız bir durum söz konusu olabilir.

Demo

MediaPipe LLM demosunda üç normal kılıf depolama yöntemini ve uygulanan sabit disk yöntemini de görebilirsiniz.

Bonus: Büyük dosyaları parçalar halinde indirin

İnternetten büyük bir yapay zeka modeli indirmeniz gerekiyorsa indirmeyi farklı parçalara paralel hale getirin ve ardından istemcide tekrar birleştirin.

Aşağıda, kodunuzda kullanabileceğiniz bir yardımcı işlev verilmiştir. Bunu yalnızca url geçirmeniz gerekir. chunkSize (varsayılan: 5 MB), maxParallelRequests (varsayılan: 6), progressCallback işlevi (downloadedBytes ve toplam fileSize değerini raporlar) ve AbortSignal sinyali için signal isteğe bağlıdır.

Aşağıdaki işlevi projenize kopyalayabilir veya npm paketinden fetch-in-chunks paketini yükleyebilirsiniz.

async function fetchInChunks(
  url,
  chunkSize = 5 * 1024 * 1024,
  maxParallelRequests = 6,
  progressCallback = null,
  signal = null
) {
  // Helper function to get the size of the remote file using a HEAD request
  async function getFileSize(url, signal) {
    const response = await fetch(url, { method: 'HEAD', signal });
    if (!response.ok) {
      throw new Error('Failed to fetch the file size');
    }
    const contentLength = response.headers.get('content-length');
    if (!contentLength) {
      throw new Error('Content-Length header is missing');
    }
    return parseInt(contentLength, 10);
  }

  // Helper function to fetch a chunk of the file
  async function fetchChunk(url, start, end, signal) {
    const response = await fetch(url, {
      headers: { Range: `bytes=${start}-${end}` },
      signal,
    });
    if (!response.ok && response.status !== 206) {
      throw new Error('Failed to fetch chunk');
    }
    return await response.arrayBuffer();
  }

  // Helper function to download chunks with parallelism
  async function downloadChunks(
    url,
    fileSize,
    chunkSize,
    maxParallelRequests,
    progressCallback,
    signal
  ) {
    let chunks = [];
    let queue = [];
    let start = 0;
    let downloadedBytes = 0;

    // Function to process the queue
    async function processQueue() {
      while (start < fileSize) {
        if (queue.length < maxParallelRequests) {
          let end = Math.min(start + chunkSize - 1, fileSize - 1);
          let promise = fetchChunk(url, start, end, signal)
            .then((chunk) => {
              chunks.push({ start, chunk });
              downloadedBytes += chunk.byteLength;

              // Update progress if callback is provided
              if (progressCallback) {
                progressCallback(downloadedBytes, fileSize);
              }

              // Remove this promise from the queue when it resolves
              queue = queue.filter((p) => p !== promise);
            })
            .catch((err) => {              
              throw err;              
            });
          queue.push(promise);
          start += chunkSize;
        }
        // Wait for at least one promise to resolve before continuing
        if (queue.length >= maxParallelRequests) {
          await Promise.race(queue);
        }
      }

      // Wait for all remaining promises to resolve
      await Promise.all(queue);
    }

    await processQueue();

    return chunks.sort((a, b) => a.start - b.start).map((chunk) => chunk.chunk);
  }

  // Get the file size
  const fileSize = await getFileSize(url, signal);

  // Download the file in chunks
  const chunks = await downloadChunks(
    url,
    fileSize,
    chunkSize,
    maxParallelRequests,
    progressCallback,
    signal
  );

  // Stitch the chunks together
  const blob = new Blob(chunks);

  return blob;
}

export default fetchInChunks;

Sizin için doğru yöntemi seçin

Bu rehberde, yapay zeka modellerini tarayıcıda etkili bir şekilde önbelleğe almayla ilgili çeşitli yöntemler ele alınmıştır. Bu yöntem, kullanıcının deneyimini ve uygulamanızın performansını iyileştirmek açısından çok önemlidir. Chrome depolama ekibi, yapay zeka modellerine hızlı erişim sağlamak, yükleme sürelerini kısaltmak ve yanıt verme süresini iyileştirmek amacıyla optimum performans için Cache API'yi önerir.

OPFS ve IndexedDB daha az kullanılabilir seçeneklerdir. Verilerin depolanabilmesi için OPFS ve IndexedDB API'lerinin verileri serileştirmesi gerekir. IndexedDB'nin veriler alındığında verilerde seri durumdan çıkarması da gerekir. Bu, bu verileri büyük modelleri depolamak için en kötü yer haline getirir.

File System Access API, niş uygulamalar için kullanıcının cihazındaki dosyalara doğrudan erişim imkanı sunar. Bu özellik, kendi AI modellerini yöneten kullanıcılar için idealdir.

Yapay zeka modelinizin güvenliğini sağlamanız gerekiyorsa modelinizi sunucuda tutun. İstemcide depolandıktan sonra, Geliştirici Araçları veya OFPS DevTools uzantısı ile hem Cache hem de IndexedDB'den veri ayıklamak çok basittir. Bu depolama API'lerinin güvenlikleri doğası gereği eşittir. Modelin şifrelenmiş bir sürümünü depolamak isteyebilirsiniz. Ancak daha sonra, müdahale edilmesi mümkün olabilecek şifre çözme anahtarını istemciye aktarmanız gerekir. Bu, kötü bir oyuncunun modelini çalmanın biraz daha zor olduğu, ancak imkansız olmadığı anlamına gelir.

Uygulamanızın şartlarına, hedef kitle davranışına ve kullanılan yapay zeka modellerinin özelliklerine uygun bir önbelleğe alma stratejisi seçmenizi öneririz. Bu, uygulamalarınızın çeşitli ağ koşulları ve sistem kısıtlamaları altında duyarlı ve sağlam olmasını sağlar.


Teşekkür

Bu inceleme; Joshua Bell, Reilly Grant, Evan Stade, Nathan Memmott, Austin Sullivan, Etienne Noël, André Bandarra, Alexandra Klepper, François Beaufort, Paul Kinlan ve Rachel Andrew tarafından incelenmiştir.