โมเดล AI ส่วนใหญ่มีสิ่งหนึ่งที่เหมือนกันคือมีขนาดใหญ่พอสมควรสำหรับทรัพยากรที่โอนผ่านอินเทอร์เน็ต โมเดลการตรวจจับวัตถุ MediaPipe ที่เล็กที่สุด (SSD MobileNetV2 float16
) มีน้ำหนัก 5.6 MB และโมเดลที่ใหญ่ที่สุดมีน้ำหนักประมาณ 25 MB
LLM แบบโอเพนซอร์ส gemma-2b-it-gpu-int4.bin
มีขนาด 1.35 GB ซึ่งถือว่าเล็กมากสำหรับ LLM
โมเดล Generative AI สามารถมีขนาดมหาศาล นี่จึงเป็นเหตุผลที่ปัจจุบันมีการใช้ AI อย่างแพร่หลายในระบบคลาวด์ มีแอปจำนวนมากขึ้นเรื่อยๆ ที่ใช้โมเดลที่ได้รับการเพิ่มประสิทธิภาพสูงในอุปกรณ์โดยตรง แม้จะมีการสาธิต 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
โมเดล AI แต่ละเวอร์ชันที่เผยแพร่เป็นทรัพยากรแบบคงที่ เนื้อหาที่ไม่มีการเปลี่ยนแปลงควรมี max-age
ยาวๆ ร่วมกับ แคชบัสเตอร์ ใน URL คำขอ หากจำเป็นต้องอัปเดตโมเดล คุณต้องให้ URL ใหม่
เมื่อผู้ใช้โหลดหน้าเว็บซ้ำ ไคลเอ็นต์จะส่งคำขอตรวจสอบอีกครั้ง แม้ว่าเซิร์ฟเวอร์จะทราบว่าเนื้อหานั้นเสถียรแล้วก็ตาม คำสั่ง immutable
บ่งชี้อย่างชัดเจนว่าไม่จำเป็นต้องตรวจสอบอีกครั้งเนื่องจากเนื้อหาจะไม่เปลี่ยนแปลง เบราว์เซอร์และแคชตัวกลางหรือพร็อกซีเซิร์ฟเวอร์ไม่ได้รองรับคำสั่ง immutable
ในวงกว้าง แต่เมื่อรวมคำสั่ง immutable
กับคำสั่ง max-age
ที่เป็นที่คุ้นเคยกันโดยทั่วไป คุณจะมั่นใจได้ถึงความสามารถในการใช้งานร่วมกันสูงสุด คำสั่งการตอบกลับ public
ระบุว่าเก็บการตอบกลับไว้ในแคชที่แชร์ได้
แคชโมเดล AI ฝั่งไคลเอ็นต์
เมื่อแสดงโมเดล AI คุณควรแคชโมเดลในเบราว์เซอร์อย่างชัดเจน วิธีนี้ช่วยให้มั่นใจได้ว่าข้อมูลโมเดลจะพร้อมใช้งานหลังจากที่ผู้ใช้โหลดแอปซ้ำ
คุณใช้เทคนิคต่างๆ ต่อไปนี้เพื่อบรรลุเป้าหมายนี้ได้ สำหรับตัวอย่างโค้ดต่อไปนี้ สมมติว่าไฟล์โมเดลแต่ละไฟล์จัดเก็บไว้ในออบเจ็กต์ Blob
ที่ชื่อ blob
ในหน่วยความจำ
ตัวอย่างโค้ดแต่ละรายการจะมีคำอธิบายประกอบด้วยเมธอด performance.mark()
และ performance.measure()
เพื่อให้เข้าใจประสิทธิภาพ การวัดเหล่านี้ขึ้นอยู่กับอุปกรณ์และไม่สามารถนําไปใช้กับอุปกรณ์อื่นๆ ได้
คุณสามารถเลือกใช้ API รายการใดรายการหนึ่งต่อไปนี้เพื่อแคชโมเดล AI ในเบราว์เซอร์ได้ Cache API, Origin Private File System API และ IndexedDB API คําแนะนําทั่วไปคือให้ใช้ Cache API แต่คําแนะนํานี้จะกล่าวถึงข้อดีและข้อเสียของตัวเลือกทั้งหมด
Cache API
Cache API มีที่จัดเก็บข้อมูลถาวรสำหรับคู่ออบเจ็กต์ Request
และ Response
ที่แคชไว้ในหน่วยความจําแบบถาวร แม้ว่าจะระบุไว้ในข้อกำหนดของ Service Worker แต่คุณก็ใช้ API นี้จากเธรดหลักหรือผู้ปฏิบัติงานทั่วไปได้ หากต้องการใช้นอกบริบทของ Service Worker ให้เรียกใช้เมธอด 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;
}
};
API ระบบไฟล์ส่วนตัวต้นทาง
ระบบไฟล์ส่วนตัวต้นทาง (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 API
IndexedDB เป็นมาตรฐานที่จัดตั้งขึ้นอย่างดีสำหรับการจัดเก็บข้อมูลแบบไม่เจาะจงในลักษณะถาวรในเบราว์เซอร์ IndexedDB เป็นที่รู้จักอย่างแพร่หลายว่ามี API ที่ซับซ้อน แต่การใช้ไลบรารี Wrapper เช่น 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;
}
};
วิธีการเหล่านี้ใช้ร่วมกันได้ อาจมีกรณีที่คุณทั้งแคชโมเดลในเบราว์เซอร์อย่างชัดเจนและใช้โมเดลจากฮาร์ดดิสก์ของผู้ใช้
สาธิต
คุณดูวิธีการจัดเก็บเคสปกติทั้ง 3 วิธีและวิธีการจัดเก็บในฮาร์ดดิสก์ได้ในการการสาธิต LLM ของ MediaPipe
โบนัส: ดาวน์โหลดไฟล์ขนาดใหญ่เป็นชิ้นๆ
หากต้องการดาวน์โหลดโมเดล AI ขนาดใหญ่จากอินเทอร์เน็ต ให้แบ่งการดาวน์โหลดออกเป็นหลายส่วนพร้อมกัน แล้วต่อกันอีกครั้งบนไคลเอ็นต์
นี่คือฟังก์ชันตัวช่วยที่คุณใช้ในโค้ดได้ คุณเพียงแค่ต้องส่ง url
เท่านั้น chunkSize
(ค่าเริ่มต้น: 5MB), 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;
เลือกวิธีการที่เหมาะกับคุณ
คู่มือนี้อธิบายวิธีการต่างๆ ในการแคชโมเดล AI ในเบราว์เซอร์อย่างมีประสิทธิภาพ ซึ่งเป็นงานที่สําคัญในการปรับปรุงประสบการณ์ของผู้ใช้และประสิทธิภาพของแอป ทีมพื้นที่เก็บข้อมูลของ Chrome ขอแนะนําให้ใช้ Cache API เพื่อประสิทธิภาพที่ดีที่สุด เพื่อให้เข้าถึงโมเดล AI ได้อย่างรวดเร็ว ลดเวลาในการโหลด และปรับปรุงการตอบสนอง
OPFS และ IndexedDB เป็นตัวเลือกที่ใช้งานได้น้อยกว่า API ของ OPFS และ IndexedDB ต้องเรียงลำดับข้อมูลก่อนจึงจะจัดเก็บได้ IndexedDB ยังต้องทำการดีซีเรียลไลซ์ข้อมูลเมื่อดึงออกมา ทำให้เป็นตำแหน่งที่แย่ที่สุดในการจัดเก็บโมเดลขนาดใหญ่
สำหรับแอปพลิเคชันแบบเฉพาะกลุ่ม File System Access API มีการเข้าถึงไฟล์โดยตรงในอุปกรณ์ของผู้ใช้ ซึ่งเหมาะสำหรับผู้ใช้ที่จัดการโมเดล AI ของตนเอง
หากต้องการรักษาความปลอดภัยให้โมเดล AI ให้เก็บโมเดลไว้บนเซิร์ฟเวอร์ เมื่อจัดเก็บไว้ในไคลเอ็นต์แล้ว คุณจะดึงข้อมูลจากทั้งแคชและ IndexedDB ได้อย่างง่ายดายด้วยเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์หรือส่วนขยายเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ OFPS API พื้นที่เก็บข้อมูลเหล่านี้มีความปลอดภัยเท่าๆ กันโดยพื้นฐาน คุณอาจต้องการจัดเก็บโมเดลเวอร์ชันที่เข้ารหัส แต่จะต้องส่งคีย์การถอดรหัสให้กับไคลเอ็นต์ ซึ่งอาจถูกขัดขวางได้ ซึ่งหมายความว่าการที่ผู้ไม่ประสงค์ดีขโมย โมเดลของคุณจะทำได้ยากขึ้นเล็กน้อย แต่ก็เป็นไปไม่ได้
เราขอแนะนําให้คุณเลือกกลยุทธ์การแคชที่สอดคล้องกับข้อกําหนดของแอป พฤติกรรมของกลุ่มเป้าหมาย และลักษณะของโมเดล AI ที่ใช้ วิธีนี้ช่วยให้มั่นใจได้ว่าแอปพลิเคชันของคุณจะมีการตอบสนองและมีประสิทธิภาพภายใต้สภาพเครือข่ายและข้อจำกัดของระบบที่หลากหลาย
ขอขอบคุณ
บทความนี้ได้รับการตรวจสอบโดย Joshua Bell, Reilly Grant, Evan Stade, Nathan Memmott, Austin Sullivan, Etienne Noël, André Bandarra, Alexandra Klepper, François Beaufort, Paul Kinlan และ Rachel Andrew