تشترك معظم نماذج الذكاء الاصطناعي (AI) في شيء واحد على الأقل، وهو
كبير جدًا بالنسبة لمورد
نقلها عبر الإنترنت. أصغر نموذج لاكتشاف عناصر MediaPipe
وزن (SSD MobileNetV2 float16
) 5.6 ميغابايت
وأكبرها حوالي 25 ميغابايت
النموذج اللغوي الكبير المفتوح المصدر
gemma-2b-it-gpu-int4.bin
على 1.35 غيغابايت، وهي تعتبر صغيرة جدًا للنماذج اللغوية الكبيرة.
يمكن أن تكون نماذج الذكاء الاصطناعي التوليدي هائلة. لهذا السبب يحدث الكثير من استخدام الذكاء الاصطناعي اليوم
في السحابة الإلكترونية. يتزايد استخدام التطبيقات لنماذج محسَّنة بدرجة عالية مباشرةً
على الجهاز فقط. أثناء تشغيل عروض توضيحية للنماذج اللغوية الكبيرة (LLM) في المتصفّح
إليك بعض الأمثلة على مستوى الإنتاج لنماذج أخرى تعمل في
المتصفح:
- يستخدم Adobe Photoshop نسخة مختلفة من طراز
Conv2D
على الجهاز فقط لاستخدام أداة اختيار الكائنات الذكية. - يستخدم Google Meet إصدارًا محسَّنًا من نموذج
MobileNetV3-small
. لتصنيف الأشخاص بفضل ميزة تعتيم الخلفية. - تستخدم شركة Tokopedia نموذج
MediaPipeFaceDetector-TFJS
. للتعرّف على الوجوه في الوقت الفعلي، وذلك لمنع عمليات الاشتراك غير الصالحة في خدمتها. - تسمح Google Colab للمستخدمين باستخدام النماذج من القرص الثابت. في أوراق ملاحظات Colab.
لجعل عمليات التشغيل المستقبلية للتطبيقات أسرع، يجب عليك التخزين المؤقت بيانات النموذج على الجهاز فقط، بدلاً من الاعتماد على متصفح 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
توجيه الاستجابة إلى أنه يمكن تخزين الرد في ذاكرة تخزين مؤقت مشتركة.
نماذج الذكاء الاصطناعي في ذاكرة التخزين المؤقت من جهة العميل
عندما تعرض أحد نماذج الذكاء الاصطناعي (AI)، من المهم أن يتم تخزين النموذج مؤقتًا بشكل صريح في المتصفح. يضمن هذا توفر بيانات النموذج بسهولة بعد إعادة تحميل المستخدم التطبيق.
وهناك عدد من الأساليب التي يمكنك استخدامها لتحقيق ذلك. بالنسبة إلى ما يلي
لعينات التعليمات البرمجية، لنفترض أن كل ملف نموذج مخزن في
كائن Blob
باسم blob
في الذاكرة.
ولفهم الأداء، تتم إضافة تعليق توضيحي لكل نموذج من التعليمات البرمجية
performance.mark()
وperformance.measure()
الطرق. وتعتمد هذه المقاييس على الجهاز وليس قابلة للتعميم.
يمكنك اختيار استخدام إحدى واجهات برمجة التطبيقات التالية لتخزين نماذج الذكاء الاصطناعي في المتصفّح: 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" تتساوى واجهات برمجة التطبيقات للتخزين بطبيعتها من حيث الأمان. قد تميل إلى تقوم بتخزين نسخة مشفرة من النموذج، ولكن عليك بعد ذلك الحصول على طريقة فك التشفير مفتاح العميل، والذي يمكن اعتراضه. وهذا يعني محاولة جهة مسيئة تكون سرقة نموذجك أكثر صعوبة بعض الشيء، ولكنها ليست مستحيلة.
ننصحك باختيار استراتيجية للتخزين المؤقت تتوافق مع المتطلبات وسلوك الجمهور المستهدف وخصائص نماذج الذكاء الاصطناعي استخدام البيانات المختلفة. ويضمن ذلك أن تكون تطبيقاتك سريعة الاستجابة وفعّالة استنادًا إلى ظروف الشبكة وقيود النظام.
شكر وتقدير
تمت مراجعته بواسطة جوشوا بيل ورايلي غرانت وإيفان ستاد وناثان ميموت "أوستن سوليفان" و"إتيان نويل" و"أندريه باندارا" و"ألكساندرا كليبر" "فرانسوا بوفورت" و"بول كينلان" و"رايتشل أندرو".