Hầu hết các mô hình AI đều có một điểm chung: chúng có dung lượng khá lớn đối với một tài nguyên được truyền qua Internet. Mô hình phát hiện đối tượng nhỏ nhất của MediaPipe (SSD MobileNetV2 float16
) có kích thước 5,6 MB và mô hình lớn nhất có kích thước khoảng 25 MB.
LLM nguồn mở gemma-2b-it-gpu-int4.bin
có kích thước 1,35 GB và đây được coi là kích thước rất nhỏ đối với một LLM. Các mô hình AI tạo sinh có thể rất lớn. Đây là lý do khiến nhiều hoạt động sử dụng AI hiện nay diễn ra trên đám mây. Ngày càng có nhiều ứng dụng chạy các mô hình được tối ưu hoá cao ngay trên thiết bị. Mặc dù có các bản minh hoạ về LLM chạy trong trình duyệt, nhưng đây là một số ví dụ cấp sản xuất về các mô hình khác chạy trong trình duyệt:
- Adobe Photoshop chạy một biến thể của mô hình
Conv2D
trên thiết bị cho công cụ chọn đối tượng thông minh. - Google Meet chạy phiên bản được tối ưu hoá của mô hình
MobileNetV3-small
để phân đoạn người cho tính năng làm mờ nền. - Tokopedia chạy mô hình
MediaPipeFaceDetector-TFJS
để phát hiện khuôn mặt theo thời gian thực nhằm ngăn chặn các lượt đăng ký không hợp lệ cho dịch vụ của mình. - Google Colab cho phép người dùng sử dụng các mô hình từ ổ đĩa cứng trong sổ tay Colab.
Để các lần khởi chạy ứng dụng trong tương lai diễn ra nhanh hơn, bạn nên lưu dữ liệu mô hình vào bộ nhớ đệm một cách rõ ràng trên thiết bị thay vì dựa vào bộ nhớ đệm HTTP ngầm của trình duyệt.
Mặc dù hướng dẫn này sử dụng gemma-2b-it-gpu-int4.bin model
để tạo một chatbot, nhưng bạn có thể khái quát hoá phương pháp này cho phù hợp với các mô hình và trường hợp sử dụng khác trên thiết bị. Cách phổ biến nhất để kết nối một ứng dụng với một mô hình là phân phát mô hình cùng với các tài nguyên còn lại của ứng dụng. Bạn cần phải tối ưu hoá việc phân phối.
Định cấu hình tiêu đề bộ nhớ đệm phù hợp
Nếu phân phát các mô hình AI từ máy chủ của mình, bạn cần định cấu hình tiêu đề Cache-Control
chính xác. Ví dụ sau đây cho thấy một chế độ cài đặt mặc định vững chắc mà bạn có thể xây dựng dựa trên nhu cầu của ứng dụng.
Cache-Control: public, max-age=31536000, immutable
Mỗi phiên bản đã phát hành của một mô hình AI là một tài nguyên tĩnh. Nội dung không bao giờ thay đổi phải được cung cấp một max-age
dài kết hợp với cache busting trong URL yêu cầu. Nếu cần cập nhật mô hình, bạn phải cung cấp một URL mới.
Khi người dùng tải lại trang, máy khách sẽ gửi yêu cầu xác thực lại, mặc dù máy chủ biết rằng nội dung ổn định. Chỉ thị immutable
cho biết rõ ràng rằng việc xác thực lại là không cần thiết, vì nội dung sẽ không thay đổi. Lệnh immutable
không được hỗ trợ rộng rãi bởi các trình duyệt và bộ nhớ đệm trung gian hoặc máy chủ proxy, nhưng bằng cách kết hợp lệnh này với lệnh max-age
được hiểu trên toàn cầu, bạn có thể đảm bảo khả năng tương thích tối đa. Chỉ thị phản hồi public
cho biết rằng phản hồi có thể được lưu trữ trong bộ nhớ đệm dùng chung.

Cache-Control
trong quá trình sản xuất do Hugging Face gửi khi yêu cầu một mô hình AI. (Nguồn) Lưu mô hình AI vào bộ nhớ đệm phía máy khách
Khi phân phát một mô hình AI, bạn cần lưu rõ ràng mô hình vào bộ nhớ đệm trong trình duyệt. Điều này đảm bảo dữ liệu mô hình luôn có sẵn sau khi người dùng tải lại ứng dụng.
Bạn có thể sử dụng một số kỹ thuật để đạt được mục tiêu này. Đối với các mẫu mã sau đây, giả sử mỗi tệp mô hình được lưu trữ trong một đối tượng Blob
có tên là blob
trong bộ nhớ.
Để hiểu rõ hiệu suất, mỗi mẫu mã đều được chú thích bằng performance.mark()
và phương thức performance.measure()
. Các biện pháp này phụ thuộc vào thiết bị và không thể khái quát hoá.

Bạn có thể chọn sử dụng một trong các API sau để lưu mô hình AI vào bộ nhớ đệm trong trình duyệt: Cache API, Origin Private File System API và IndexedDB API. Đề xuất chung là sử dụng Cache API, nhưng hướng dẫn này sẽ thảo luận về ưu và nhược điểm của tất cả các lựa chọn.
Cache API
Cache API cung cấp bộ nhớ liên tục cho các cặp đối tượng Request
và Response
được lưu vào bộ nhớ đệm trong bộ nhớ có thời gian tồn tại lâu dài. Mặc dù được xác định trong thông số kỹ thuật của Service Worker, bạn vẫn có thể sử dụng API này từ luồng chính hoặc một worker thông thường. Để sử dụng phương thức này bên ngoài ngữ cảnh của worker dịch vụ, hãy gọi phương thức Cache.put()
bằng một đối tượng Response
tổng hợp, kết hợp với một URL tổng hợp thay vì đối tượng Request
.
Hướng dẫn này giả định một blob
trong bộ nhớ. Sử dụng một URL giả làm khoá bộ nhớ đệm và một Response
giả tạo dựa trên blob
. Nếu tải trực tiếp mô hình xuống, bạn sẽ sử dụng Response
mà bạn nhận được khi đưa ra yêu cầu fetch()
.
Ví dụ: dưới đây là cách lưu trữ và khôi phục một tệp mô hình bằng 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) là một tiêu chuẩn tương đối mới cho một điểm cuối lưu trữ. Nó là riêng tư đối với nguồn gốc của trang và do đó người dùng không nhìn thấy được, không giống như hệ thống tệp thông thường. Thư viện này cung cấp quyền truy cập vào một tệp đặc biệt được tối ưu hoá cao về hiệu suất và cho phép ghi vào nội dung của tệp đó.
Ví dụ: dưới đây là cách lưu trữ và khôi phục một tệp mô hình trong 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; } };
API IndexedDB
IndexedDB là một tiêu chuẩn đã được thiết lập để lưu trữ dữ liệu tuỳ ý theo cách liên tục trong trình duyệt. IndexedDB nổi tiếng vì có API hơi phức tạp, nhưng bằng cách sử dụng một thư viện trình bao bọc như idb-keyval, bạn có thể coi IndexedDB như một kho khoá-giá trị cổ điển.
Ví dụ:
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; } };
Đánh dấu bộ nhớ là được duy trì
Gọi navigator.storage.persist()
khi kết thúc bất kỳ phương thức lưu vào bộ nhớ đệm nào trong số này để yêu cầu cấp quyền sử dụng bộ nhớ liên tục. Phương thức này trả về một lời hứa phân giải thành true
nếu quyền được cấp và false
nếu không. Trình duyệt có thể chấp nhận hoặc không chấp nhận yêu cầu, tuỳ thuộc vào các quy tắc cụ thể của trình duyệt.
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); } }
Trường hợp đặc biệt: Sử dụng mô hình trên ổ đĩa cứng
Bạn có thể tham chiếu các mô hình AI trực tiếp từ ổ cứng của người dùng thay vì bộ nhớ trình duyệt. Kỹ thuật này có thể giúp các ứng dụng tập trung vào nghiên cứu thể hiện tính khả thi của việc chạy các mô hình nhất định trong trình duyệt hoặc cho phép các nghệ sĩ sử dụng các mô hình tự huấn luyện trong các ứng dụng sáng tạo chuyên nghiệp.
API Truy cập hệ thống tệp
Với File System Access API, bạn có thể mở các tệp trên ổ cứng và lấy một FileSystemFileHandle mà bạn có thể duy trì trong IndexedDB.
Với mẫu này, người dùng chỉ cần cấp quyền truy cập vào tệp mô hình một lần. Nhờ quyền được duy trì, người dùng có thể chọn cấp quyền truy cập vĩnh viễn vào tệp. Sau khi tải lại ứng dụng và một cử chỉ bắt buộc của người dùng (chẳng hạn như thao tác nhấp chuột), FileSystemFileHandle
có thể được khôi phục từ IndexedDB khi có quyền truy cập vào tệp trên ổ cứng.
Quyền truy cập vào tệp được truy vấn và yêu cầu nếu cần, điều này giúp quá trình tải lại trong tương lai diễn ra liền mạch. Ví dụ sau đây cho biết cách lấy một giá trị nhận dạng cho một tệp trên ổ cứng, sau đó lưu trữ và khôi phục giá trị nhận dạng đó.
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; } };
Các phương thức này không loại trừ lẫn nhau. Có thể xảy ra trường hợp bạn vừa lưu một mô hình vào bộ nhớ đệm một cách rõ ràng trong trình duyệt, vừa sử dụng một mô hình từ ổ cứng của người dùng.
Bản minh hoạ
Bạn có thể xem cả 3 phương thức lưu trữ trường hợp thông thường và phương thức ổ cứng được triển khai trong bản minh hoạ LLM của MediaPipe.
Mẹo hay: Tải một tệp lớn xuống theo từng phần
Nếu bạn cần tải một mô hình AI lớn xuống từ Internet, hãy song song hoá quá trình tải xuống thành các khối riêng biệt, sau đó ghép lại với nhau trên máy khách.
Sau đây là một hàm trợ giúp mà bạn có thể dùng trong mã của mình. Bạn chỉ cần truyền url
cho nó. chunkSize
(mặc định: 5 MB), maxParallelRequests
(mặc định: 6), hàm progressCallback
(báo cáo về downloadedBytes
và tổng fileSize
) và signal
cho tín hiệu AbortSignal
đều là không bắt buộc.
Bạn có thể sao chép hàm sau trong dự án của mình hoặc cài đặt gói fetch-in-chunks
từ gói 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;
Chọn phương thức phù hợp với bạn
Hướng dẫn này đã khám phá nhiều phương pháp để lưu hiệu quả các mô hình AI vào bộ nhớ đệm trong trình duyệt. Đây là một nhiệm vụ quan trọng để nâng cao trải nghiệm người dùng và hiệu suất của ứng dụng. Nhóm lưu trữ của Chrome đề xuất sử dụng Cache API để đạt hiệu suất tối ưu, đảm bảo truy cập nhanh vào các mô hình AI, giảm thời gian tải và cải thiện khả năng phản hồi.
OPFS và IndexedDB là những lựa chọn ít hữu ích hơn. OPFS và API IndexedDB cần chuyển đổi dữ liệu thành chuỗi trước khi có thể lưu trữ. IndexedDB cũng cần phải chuyển đổi dữ liệu tuần tự hoá thành dữ liệu gốc khi truy xuất, khiến đây là nơi tệ nhất để lưu trữ các mô hình lớn.
Đối với các ứng dụng chuyên biệt, File System Access API cung cấp quyền truy cập trực tiếp vào các tệp trên thiết bị của người dùng, phù hợp với những người dùng quản lý các mô hình AI của riêng họ.
Nếu bạn cần bảo mật mô hình AI, hãy giữ mô hình đó trên máy chủ. Sau khi được lưu trữ trên máy khách, việc trích xuất dữ liệu từ cả Bộ nhớ đệm và IndexedDB bằng DevTools hoặc tiện ích DevTools OFPS là điều không khó. Các API lưu trữ này vốn có mức độ bảo mật tương đương nhau. Bạn có thể muốn lưu trữ một phiên bản đã mã hoá của mô hình, nhưng sau đó bạn cần lấy khoá giải mã cho ứng dụng, khoá này có thể bị chặn. Điều này có nghĩa là nỗ lực của kẻ xấu nhằm đánh cắp mô hình của bạn sẽ khó khăn hơn một chút, nhưng không phải là không thể.
Bạn nên chọn một chiến lược lưu vào bộ nhớ đệm phù hợp với các yêu cầu của ứng dụng, hành vi của đối tượng mục tiêu và đặc điểm của các mô hình AI được dùng. Điều này đảm bảo các ứng dụng của bạn phản hồi nhanh và mạnh mẽ trong nhiều điều kiện mạng và các hạn chế của hệ thống.
Lời cảm ơn
Nội dung này được Joshua Bell, Reilly Grant, Evan Stade, Nathan Memmott, Austin Sullivan, Etienne Noël, André Bandarra, Alexandra Klepper, François Beaufort, Paul Kinlan và Rachel Andrew xem xét.