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

Thomas Steiner
Thomas Steiner

Çoğu yapay zeka modelinin ortak bir özelliği vardır: İnternet üzerinden aktarılan bir kaynak için oldukça büyüktür. En küçük MediaPipe nesne algılama modeli (SSD MobileNetV2 float16) 5,6 MB, en büyük model ise yaklaşık 25 MB'tır.

Açık kaynak LLM gemma-2b-it-gpu-int4.bin 1,35 GB'tır ve bu, bir LLM için çok küçük kabul edilir. Üretken yapay zeka modelleri çok büyük olabilir. Bu nedenle, günümüzde yapay zekanın büyük bir kısmı bulutta kullanılıyor. Uygulamalar, giderek daha fazla sayıda yüksek oranda optimize edilmiş modeli doğrudan cihazda çalıştırıyor. Tarayıcıda çalışan LLM'lerin demoları mevcut olsa da tarayıcıda çalışan diğer modellerin üretim sınıfı örneklerini aşağıda bulabilirsiniz:

Web'de Adobe Photoshop'ta yapay zeka destekli nesne seçim aracı açıkken iki zürafa ve bir ay olmak üzere üç nesne seçili.

Uygulamalarınızın gelecekteki lansmanlarını hızlandırmak için, örtülü HTTP tarayıcı önbelleğini kullanmak yerine model verilerini cihaz üzerinde açıkça önbelleğe almanız gerekir.

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

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

Sunucunuzdan yapay zeka modelleri sunuyorsanız doğru Cache-Control başlığını yapılandırmanız ö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 yapay zeka modelinin yayınlanan her sürümü statik bir kaynaktır. Hiçbir zaman değişmeyen içeriklere, istek URL'sinde önbelleği bozma ile birlikte uzun bir max-age eklenmelidir. Modeli güncellemeniz gerekiyorsa modele yeni bir URL vermeniz gerekir.

Sunucu içeriğin kararlı olduğunu bilse bile kullanıcı sayfayı yeniden yüklediğinde istemci bir yeniden doğrulama isteği gönderir. immutable yönergesinde, içerik değişmeyeceği için yeniden doğrulamanın gerekmediği açıkça belirtilmiştir. immutable yönergesi, tarayıcılar ve aracı önbelleğe alma veya proxy sunucuları tarafından yaygın olarak desteklenmez. Ancak bu yönergeyi evrensel olarak anlaşılan max-age yönergesi ile birleştirerek 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 üretim Cache-Control başlıklarını gösterir. (Kaynak)

Yapay zeka modellerini istemci tarafında önbelleğe alma

Bir yapay zeka modelini yayınlarken modeli tarayıcıda açıkça önbelleğe almak önemlidir. Bu sayede, kullanıcı uygulamayı yeniden yükledikten sonra model verilerinin hazır olması sağlanır.

Bunu başarmak için kullanabileceğiniz çeşitli teknikler vardır. Aşağıdaki kod örneklerinde, her model dosyasının bellekte blob adlı bir Blob nesnesinde depolandığı varsayılır.

Performansı anlamak için her kod örneği, performance.mark() ve performance.measure() yöntemleriyle ek açıklamaya sahiptir. Bu ölçümler cihaza bağlıdır ve genelleştirilemez.

Chrome Geliştirici Araçları Uygulama > Depolama bölümünde, IndexedDB, Önbellek depolama alanı ve Dosya Sistemi segmentlerini içeren kullanım diyagramını inceleyin. Her segmentin 1.354 megabayt veri kullandığı, toplamda 4.063 megabayt veri kullanıldığı görülüyor.

Tarayıcıda yapay zeka modellerini önbelleğe almak için aşağıdaki API'lerden birini kullanmayı seçebilirsiniz: Cache API, Origin Private File System API ve IndexedDB API. Genel öneri, Cache API'yi kullanmaktır ancak bu kılavuzda tüm seçeneklerin avantajları ve dezavantajları ele alınmaktadır.

Cache API

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ığında veya normal bir çalışanda kullanabilirsiniz. Hizmet çalışanı bağlamının dışında kullanmak için Cache.put() yöntemini, Request nesnesi yerine sentetik bir URL ile eşleştirilmiş sentetik bir Response nesnesi ile çağırın.

Bu kılavuzda, bellek içi bir blob olduğu varsayılmaktadır. Önbelleğe alma anahtarı olarak sahte bir URL ve blob'ye dayalı sentetik bir Response kullanın. Modeli doğrudan indirirseniz fetch() isteği göndererek aldığınız Response değerini kullanırsınız.

Örneğin, Cache API ile bir model dosyasını nasıl depolayacağınızı ve geri yükleyeceğinizi aşağıda görebilirsiniz.

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

Origin Private File System API

Kaynak Özel Dosya Sistemi (OPFS), depolama uç noktası için nispeten yeni bir standarttır. Normal dosya sisteminden farklı olarak, sayfanın kaynağına özeldir ve bu nedenle kullanıcı tarafından görülemez. Performans için 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ı OPFS'de depolama ve geri yükleme işlemini burada bulabilirsiniz.

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

IndexedDB, tarayıcıda rastgele verileri kalıcı olarak depolamak için iyi bilinen bir standarttır. Bir miktar karmaşık API'si nedeniyle kötü bir üne sahip olsa da idb-keyval gibi bir sarmalayıcı kitaplığı kullanarak IndexedDB'i klasik bir anahtar/değer mağazası 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şaretleme

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

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 yapay zeka 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 göstermesine yardımcı olabilir veya sanatçıların uzman yaratıcılık uygulamalarında kendi kendine eğitilmiş modelleri kullanmasına olanak tanıyabilir.

File System Access API

File System Access API ile sabit diskteki dosyaları açabilir ve IndexedDB'de kalıcı olarak saklayabileceğ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 vermeyi seçebilir. Uygulamayı yeniden yükledikten ve fare tıklaması gibi gerekli bir kullanıcı hareketi gerçekleştirdikten sonra FileSystemFileHandle, sabit diskteki dosyaya erişerek IndexedDB'den geri yüklenebilir.

Dosya erişim izinleri sorgulanır ve gerekirse istenir. Bu sayede, gelecekteki yeniden yükleme işlemleri sorunsuz bir şekilde gerçekleşir. Aşağıdaki örnekte, bir dosyanın sabit diskten nasıl oluşturulacağı, ardından bu oluşturulan tutamacın 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. Bir modeli hem tarayıcıda açıkça önbelleğe almanız hem de kullanıcının sabit diskindeki bir modeli kullanmanız gerekebilir.

Demo

Normal durumdaki üç depolama yönteminin yanı sıra MediaPipe LLM demosunda uygulanan sabit disk yöntemini de görebilirsiniz.

Bonus: Büyük dosyaları parçalara ayırarak indirme

İnternetten büyük bir yapay zeka modeli indirmeniz gerekiyorsa indirme işlemini ayrı parçalara paralel olarak bölün ve ardından istemcide tekrar birleştirin.

Kodunuzda kullanabileceğiniz bir yardımcı işlev aşağıda verilmiştir. Yalnızca url değerini iletmeniz gerekir. chunkSize (varsayılan: 5 MB), maxParallelRequests (varsayılan: 6), progressCallback işlevi (downloadedBytes ve toplam fileSize hakkında rapor verir) ve AbortSignal sinyali için signal seçeneklerinin tümü isteğe bağlıdır.

Aşağıdaki işlevi projenize kopyalayabilir veya npm'den 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;

Size en uygun yöntemi seçme

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

OPFS ve IndexedDB daha az kullanılabilir seçeneklerdir. OPFS ve IndexedDB API'lerinin, verilerin depolanabilmesi için verileri serileştirmesi gerekir. IndexedDB'nin, verileri aldığında verileri seri dışına çıkarması da gerekir. Bu da büyük modelleri depolamak için en kötü yer olmasını sağlar.

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

Yapay zeka modelinizin güvenliğini sağlamanız gerekiyorsa sunucuda bırakın. İstemcide depolanan verileri DevTools veya OFPS DevTools uzantısı ile hem önbellekten hem de IndexedDB'den ayıklamak çok kolaydır. Bu depolama API'leri, güvenlik açısından temelde eşittir. Modelin şifrelenmiş bir sürümünü depolamak isteyebilirsiniz ancak bu durumda şifre çözme anahtarını istemciye göndermeniz gerekir. Bu anahtarın aktarımı sırasında müdahale edilebilir. Bu, kötü niyetli kişilerin modelinizi çalmaya çalışmasının biraz daha zor olduğu ancak imkansız olmadığı anlamına gelir.

Uygulamanızın gereksinimlerine, hedef kitle davranışına ve kullanılan yapay zeka modellerinin özelliklerine uygun bir önbelleğe alma stratejisi seçmenizi öneririz. Bu sayede, uygulamalarınız çeşitli ağ koşullarında ve sistem kısıtlamalarında duyarlı ve güçlü olur.


Teşekkür ederiz

Bu makale 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 incelendi.