โมเดล 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
กันอย่างแพร่หลาย แต่การรวมคำสั่งนี้เข้ากับคำสั่ง max-age
ที่เข้าใจกันทั่วโลกจะช่วยให้มั่นใจได้ว่ามีความเข้ากันได้สูงสุด คำสั่ง public
response บ่งบอกว่าสามารถจัดเก็บคำตอบไว้ในแคชที่แชร์ได้
แคชโมเดล 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;
}
};
Origin Private File System API
Origin Private File System (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 เป็นตัวเลือกที่ใช้งานได้น้อย OPFS และ IndexedDB API ต้องจัดรูปแบบข้อมูลก่อนจึงจะจัดเก็บได้ นอกจากนี้ 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