تخزين نماذج الذكاء الاصطناعي في ذاكرة التخزين المؤقت في المتصفّح

تشترك معظم نماذج الذكاء الاصطناعي (AI) في شيء واحد على الأقل، وهو كبير جدًا بالنسبة لمورد نقلها عبر الإنترنت. أصغر نموذج لاكتشاف عناصر MediaPipe وزن (SSD MobileNetV2 float16) 5.6 ميغابايت وأكبرها حوالي 25 ميغابايت

النموذج اللغوي الكبير المفتوح المصدر gemma-2b-it-gpu-int4.bin على 1.35 غيغابايت، وهي تعتبر صغيرة جدًا للنماذج اللغوية الكبيرة. يمكن أن تكون نماذج الذكاء الاصطناعي التوليدي هائلة. لهذا السبب يحدث الكثير من استخدام الذكاء الاصطناعي اليوم في السحابة الإلكترونية. يتزايد استخدام التطبيقات لنماذج محسَّنة بدرجة عالية مباشرةً على الجهاز فقط. أثناء تشغيل عروض توضيحية للنماذج اللغوية الكبيرة (LLM) في المتصفّح إليك بعض الأمثلة على مستوى الإنتاج لنماذج أخرى تعمل في المتصفح:

تم فتح Adobe Photoshop للويب، وهو أداة لاختيار العناصر المستندة إلى الذكاء الاصطناعي، حيث تم اختيار ثلاثة عناصر: زرافتان وقمر.

لجعل عمليات التشغيل المستقبلية للتطبيقات أسرع، يجب عليك التخزين المؤقت بيانات النموذج على الجهاز فقط، بدلاً من الاعتماد على متصفح HTTP الضمني ذاكرة التخزين المؤقت.

يستخدم هذا الدليل gemma-2b-it-gpu-int4.bin model لإنشاء روبوت دردشة، إمكانية تعميم النهج لتناسب النماذج الأخرى وحالات الاستخدام الأخرى على الجهاز فقط. أكثر الطرق شيوعًا لربط التطبيق بالنموذج هي عرض النموذج إلى جانب باقي موارد التطبيق. من الضروري تحسين التسليم.

ضبط عناوين ذاكرة التخزين المؤقت الصحيحة

إذا كنت تعرض نماذج الذكاء الاصطناعي (AI) من خادمك، من المهم ضبط نماذج الذكاء الاصطناعي Cache-Control . يوضح المثال التالي إعدادًا افتراضيًا ثابتًا، يمكنك إنشاؤه قيد التشغيل لاحتياجات تطبيقك.

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

كل نسخة تم إصدارها من نموذج الذكاء الاصطناعي هي مصدر ثابت. محتوى لا يظهر أبدًا التغييرات يجب إعطاء فترة طويلة max-age مع تنظيم ذاكرة التخزين المؤقت في عنوان URL للطلب. وإذا كنت بحاجة إلى تحديث النموذج، يجب إضافة عنوان URL جديد.

عندما يعيد المستخدم تحميل الصفحة، يرسل العميل طلب إعادة التحقق، حتى على الرغم من أنّ الخادم يعرف أنّ المحتوى مستقر. تشير رسالة الأشكال البيانية immutable مباشرةً إلى أن إعادة التحقق غير ضرورية، لأن المحتوى لن يتغير. التوجيه immutable هو غير متاحة على نطاق واسع باستخدام المتصفحات وذاكرة التخزين المؤقت الوسيطة أو الخوادم الوكيلة، ولكن من خلال دمجها مع السمة التوجيه max-age العالمي، يمكنك ضمان الحد الأقصى التوافق. public توجيه الاستجابة إلى أنه يمكن تخزين الرد في ذاكرة تخزين مؤقت مشتركة.

تعرض "أدوات مطوري البرامج في Chrome" الإصدار Cache-Control عناوين يرسلها الوجه المعانق عند طلب نموذج ذكاء اصطناعي. (المصدر)

نماذج الذكاء الاصطناعي في ذاكرة التخزين المؤقت من جهة العميل

عندما تعرض أحد نماذج الذكاء الاصطناعي (AI)، من المهم أن يتم تخزين النموذج مؤقتًا بشكل صريح في المتصفح. يضمن هذا توفر بيانات النموذج بسهولة بعد إعادة تحميل المستخدم التطبيق.

وهناك عدد من الأساليب التي يمكنك استخدامها لتحقيق ذلك. بالنسبة إلى ما يلي لعينات التعليمات البرمجية، لنفترض أن كل ملف نموذج مخزن في كائن Blob باسم blob في الذاكرة.

ولفهم الأداء، تتم إضافة تعليق توضيحي لكل نموذج من التعليمات البرمجية performance.mark() وperformance.measure() الطرق. وتعتمد هذه المقاييس على الجهاز وليس قابلة للتعميم.

في تطبيق Chrome DevTools > مساحة التخزين، مراجعة مخطط الاستخدام مع مقاطع لقاعدة البيانات المفهرسة وتخزين ذاكرة التخزين المؤقت ونظام الملفات. يبدو أنّ كل شريحة تستهلك 1354 ميغابايت من البيانات، ليصل إجمالي ذلك إلى 4, 063. ميجابايت.

يمكنك اختيار استخدام إحدى واجهات برمجة التطبيقات التالية لتخزين نماذج الذكاء الاصطناعي في المتصفّح: cache API Origin Private File System API، واجهة برمجة تطبيقات IndexedDB والاقتراح العام هو استخدام cache API، ولكن هذا الدليل يناقش مزايا وعيوب جميع الخيارات.

واجهة برمجة تطبيقات ذاكرة التخزين المؤقت

توفر Cache API مساحة تخزين دائمة لـ Request وعنصر Response الأزواج التي يتم تخزينها في ذاكرة طويلة العمر. وعلى الرغم من المحدّدة في مواصفات "مشغّلي الخدمات" يمكنك استخدام واجهة برمجة التطبيقات هذه من سلسلة التعليمات الرئيسية أو أي عامل تشغيل عادي. لاستخدامها خارج المنزل سياق عامل الخدمات، قم باستدعاء طريقة واحدة (Cache.put()) مع كائن Response اصطناعي مقترن بعنوان URL اصطناعي بدلاً من الكائن Request.

يفترض هذا الدليل وجود blob في الذاكرة. استخدام عنوان URL مزيّف كمفتاح لذاكرة التخزين المؤقت Response اصطناعية استنادًا إلى blob. إذا كنت ترغب في تنزيل ملف في النموذج، عليك استخدام Response الذي ستحصل عليه عند إنشاء fetch(). طلبك.

على سبيل المثال، إليك كيفية تخزين ملف نموذج واستعادته باستخدام Cache API.

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

واجهة برمجة تطبيقات نظام الملفات الخاصة المصدر

نظام الملفات الخاصة المصدر (OPFS) معيار صغير نسبيًا لعمر نقطة نهاية التخزين. أنها خاصة بأصل الصفحة، وبالتالي فهي غير مرئية للمستخدم، على عكس نظام الملفات العادي. وهي توفر الوصول إلى واجهة برمجة تطبيقات خاصة تم تحسينه بدرجة كبيرة من أجل الأداء ويوفر إمكانية الوصول للكتابة المحتوى.

على سبيل المثال، إليك كيفية تخزين ملف نموذج واستعادته في OPFS.

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

IndexedDB معيار راسخ لتخزين البيانات العشوائية بطريقة دائمة في المتصفح. وهي تشتهر بواجهة برمجة التطبيقات المعقدة إلى حد ما، ولكن باستخدام مكتبة برنامج تضمين، مثل idb-keyval يمكنك التعامل مع IndexedDB كمخزن كلاسيكي لقيمة المفتاح.

على سبيل المثال:

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

وضع علامة "ثابت" على مساحة التخزين

الاتصال بالرقم navigator.storage.persist() في نهاية أي من طرق التخزين المؤقت هذه لطلب إذن لاستخدام مساحة تخزين دائمة. تعرض هذه الطريقة وعدًا يتم تنفيذه إلى true في حال يتم منح الإذن، وfalse في الحالات الأخرى. المتصفح قد ينفّذ أو لا يفي بالطلب استنادًا إلى القواعد الخاصة بكل متصفّح

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

حالة خاصة: استخدام نموذج على قرص ثابت

يمكنك الرجوع إلى نماذج الذكاء الاصطناعي (AI) مباشرةً من القرص الثابت للمستخدم كبديل. إلى مساحة التخزين في المتصفّح ويمكن أن تساعد هذه التقنية التطبيقات التي تركز على الأبحاث في عرض جدوى تشغيل نماذج معينة في المتصفح، أو السماح للفنانين باستخدام نماذج مدرَّبة ذاتيًا في تطبيقات إبداعية متخصّصة.

File System Access API

باستخدام File System Access API، فيمكنك فتح ملفات من القرص الثابت والحصول على FileSystemFileHandle يمكنك الاستمرار في استخدام أداة IndexedDB.

باستخدام هذا النمط، لن يحتاج المستخدم إلا إلى منح حق الوصول إلى ملف النموذج مرة واحدة. وبفضل الأذونات المستمرة، يمكن للمستخدم اختيار منح حق الوصول إلى الملف بشكل دائم. بعد إعادة تحميل التطبيق وإيماءة المستخدم المطلوبة، مثل النقر بالماوس، يمكن استعادة FileSystemFileHandle من IndexedDB مع إمكانية الوصول إلى الملف. على القرص الثابت.

يتم الاستعلام عن أذونات الوصول إلى الملف وطلبها إذا لزم الأمر، مما يجعل هذا السلس لعمليات إعادة التحميل المستقبلية. يوضح المثال التالي كيفية الحصول على المقبض الخاص بملف من القرص الثابت، ثم تخزين المقبض واستعادته.

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

هاتان الطريقتان غير متنافيتين. قد تكون هناك حالة تمكنا فيها تخزين نموذج مؤقتًا بشكل صريح في المتصفح واستخدام نموذج من القرص الثابت للمستخدم.

عرض توضيحي

يمكنك الاطلاع على الطرق الثلاثة العادية لتخزين الحالة وطريقة القرص الثابت تم تنفيذه في الإصدار التجريبي من MediaPipe LLM.

مكافأة: تنزيل ملف كبير في مجموعات

إذا كنت بحاجة إلى تنزيل نموذج ذكاء اصطناعي (AI) كبير من الإنترنت، عليك تنزيل وقم بتنزيلها إلى أجزاء منفصلة، ثم قم بتجميعها معًا مرة أخرى على الجهاز العميل.

إليك دالة مساعدة يمكنك استخدامها في التعليمة البرمجية. عليك فقط اجتياز على url. قيمة chunkSize (القيمة التلقائية: 5 ميغابايت)، وmaxParallelRequests (الافتراضي: 6)، الدالة progressCallback (التي تقدم تقارير عن downloadedBytes والإجمالي fileSize) وsignal إشارة AbortSignal كلها اختيارية.

يمكنك نسخ الدالة التالية في مشروعك أو تثبيت حزمة fetch-in-chunks من حزمة npm.

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;

اختيار الطريقة المناسبة لك

استكشف هذا الدليل طُرقًا مختلفة للتخزين المؤقت لنماذج الذكاء الاصطناعي بشكل فعّال في المتصفح، وهو مهمة أساسية لتحسين تجربة المستخدم مع أداء تطبيقك. يوصي فريق مساحة تخزين Chrome بواجهة Cache API الأداء الأمثل لضمان الوصول السريع إلى نماذج الذكاء الاصطناعي (AI) وتقليل مدّة التحميل وتحسين سرعة الاستجابة.

يُرجى العلم أنّ خيارات OPFS وIndexedDB هي أقل قابلية للاستخدام. واجهات برمجة تطبيقات OPFS وIndexedDB إلى إنشاء تسلسل للبيانات قبل تخزينها. تحتاج قاعدة البيانات المفهرسة أيضًا إلى إلغاء تسلسل البيانات عند استردادها، مما يجعلها أسوأ مكان لتخزينها والنماذج الكبيرة.

بالنسبة إلى التطبيقات المتخصصة، تتيح واجهة برمجة التطبيقات File System Access API إمكانية الوصول المباشر إلى الملفات على جهاز المستخدم، وهو مثالي للمستخدمين الذين يديرون نماذج الذكاء الاصطناعي (AI) الخاصة بهم.

إذا كنت بحاجة إلى تأمين نموذج الذكاء الاصطناعي، يُرجى إبقاؤه على الخادم. بمجرد تخزينها على من السهل استخراج البيانات من كل من ذاكرة التخزين المؤقت وقاعدة البيانات المفهرسة "أدوات مطوري البرامج" أو إضافة "أدوات مطوري البرامج OFPS" تتساوى واجهات برمجة التطبيقات للتخزين بطبيعتها من حيث الأمان. قد تميل إلى تقوم بتخزين نسخة مشفرة من النموذج، ولكن عليك بعد ذلك الحصول على طريقة فك التشفير مفتاح العميل، والذي يمكن اعتراضه. وهذا يعني محاولة جهة مسيئة تكون سرقة نموذجك أكثر صعوبة بعض الشيء، ولكنها ليست مستحيلة.

ننصحك باختيار استراتيجية للتخزين المؤقت تتوافق مع المتطلبات وسلوك الجمهور المستهدف وخصائص نماذج الذكاء الاصطناعي استخدام البيانات المختلفة. ويضمن ذلك أن تكون تطبيقاتك سريعة الاستجابة وفعّالة استنادًا إلى ظروف الشبكة وقيود النظام.


شكر وتقدير

تمت مراجعته بواسطة جوشوا بيل ورايلي غرانت وإيفان ستاد وناثان ميموت "أوستن سوليفان" و"إتيان نويل" و"أندريه باندارا" و"ألكساندرا كليبر" "فرانسوا بوفورت" و"بول كينلان" و"رايتشل أندرو".