Większość modeli AI ma co najmniej 1 wspólną cechę: są
dość dużych w przypadku zasobów,
przesyłanych przez internet. Najmniejszy model wykrywania obiektów MediaPipe
(SSD MobileNetV2 float16
) waży 5,6 MB
a największy ma około 25 MB.
LLM typu open source
gemma-2b-it-gpu-int4.bin
i 1,35 GB, co w przypadku LLM jest uważane za bardzo mały.
Modele generatywnej AI mogą być ogromne. Właśnie dlatego AI jest dziś wykorzystywana w dużym stopniu.
w chmurze. Coraz częściej aplikacje bezpośrednio korzystają z wysoce zoptymalizowanych modeli
na urządzeniu. Wersje demonstracyjne LLM działające w przeglądarce
oto kilka przykładów klasy produkcyjnej innych modeli działających w
przeglądarka:
- Adobe Photoshop korzysta z wariantu modelu
Conv2D
na urządzeniu. - Google Meet korzysta ze zoptymalizowanej wersji modelu
MobileNetV3-small
segmentacji osób ze względu na funkcję rozmycia tła. - Tokopedia korzysta z modelu
MediaPipeFaceDetector-TFJS
, aby wykrywać twarze w czasie rzeczywistym i zapobiegać nieprawidłowej rejestracji w usłudze. - Google Colab umożliwia użytkownikom korzystanie z modeli ze swojego dysku twardego w notatnikach Colab.
Aby przyspieszyć uruchamianie aplikacji w przyszłości, należy jawnie przechowywać w pamięci podręcznej danych modelu na urządzeniu zamiast polegać na niejawnej przeglądarce HTTP pamięci podręcznej.
W tym przewodniku opisujemy gemma-2b-it-gpu-int4.bin model
przy tworzeniu czatbota,
Podejście można uogólnić, aby dopasować je do innych modeli i innych przypadków użycia
na urządzeniu. Najczęstszym sposobem łączenia aplikacji z modelem jest przesyłanie
wraz z pozostałymi zasobami aplikacji. Niezwykle ważne jest,
.
Skonfiguruj odpowiednie nagłówki pamięci podręcznej
Jeśli udostępniasz modele AI z serwera, ważne jest skonfigurowanie
Cache-Control
nagłówek. Poniższy przykład pokazuje solidne ustawienie domyślne, które można utworzyć
dostosowane do potrzeb Twojej aplikacji.
Cache-Control: public, max-age=31536000, immutable
Każda opublikowana wersja modelu AI jest zasobem statycznym. Treści, które nigdy
zmiany powinny mieć duży
max-age
w połączeniu z pomijaniem pamięci podręcznej
w adresie URL żądania. Jeśli jest konieczna aktualizacja modelu,
podaj nowy adres URL.
Gdy użytkownik odświeży stronę, klient wysyła żądanie ponownej weryfikacji, nawet
chociaż na serwerze wie, że jej zawartość jest stabilna.
immutable
wyraźnie wskazuje, że ponowna weryfikacja jest niepotrzebna, ponieważ
treści się nie zmienią. Dyrektywa immutable
jest
nie jest powszechnie obsługiwana
przez przeglądarki i pośrednią pamięć podręczną lub serwery proxy, ale przez
łącząc go z
zrozumiałej dyrektywy max-age
, możesz zapewnić maksimum
zgodność. public
dyrektywa response wskazuje, że odpowiedź można zapisać we wspólnej pamięci podręcznej.
Buforowanie modeli AI po stronie klienta
Gdy udostępniasz model AI, pamiętaj, aby jawnie zapisać go w pamięci podręcznej w przeglądarki. Dzięki temu dane modelu będą łatwo dostępne po ponownym załadowaniu użytkownika. aplikację.
Istnieje kilka metod, które można wykorzystać, aby to osiągnąć. Dla następujących
w przykładach z przykładowym kodem, załóżmy, że każdy plik modelu jest przechowywany w
Blob
obiekt o nazwie blob
w pamięci.
Aby ułatwić zrozumienie wydajności, do każdego przykładowego kodu dodano adnotację
performance.mark()
oraz performance.measure()
. Te wskaźniki zależą od urządzenia i nie można ich uogólnić.
Do buforowania modeli AI w przeglądarce możesz użyć jednego z tych interfejsów API: Cache API, Origin Private File System API oraz Interfejs API IndexedDB: Zaleca się stosowanie Cache API, ale w tym przewodniku omawiamy zalety i wady wszystkie opcje.
Interfejs API pamięci podręcznej
Interfejs Cache API udostępnia
pamięć trwała dla Request
i obiekt Response
pary przechowywane w pamięci długotrwałej. Chociaż jest to
zdefiniowane w specyfikacji mechanizmów Service Workers,
tego interfejsu API możesz używać w wątku głównym lub w zwykłej instancji roboczej. Do używania na zewnątrz
kontekstu mechanizmu Service Worker, wywołaj metodę
Metoda Cache.put()
z syntetycznym obiektem Response
sparowanym z syntetycznym adresem URL zamiast
Request
obiekt.
W tym przewodniku przyjęto założenie blob
w pamięci. Użyj fałszywego adresu URL jako klucza pamięci podręcznej
syntetyczny Response
na podstawie blob
. Gdyby pobrać plik bezpośrednio
model, należy użyć funkcji Response
, którą można uzyskać po utworzeniu fetch()
użytkownika.
Poniżej znajdziesz na przykład informacje o tym, jak zapisać i przywrócić plik modelu za pomocą interfejsu 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;
}
};
Interfejs Origin Private File System API
Origin Private File System (OPFS) to dość młody standard punktu końcowego pamięci masowej. Obiekt jest prywatny dla pochodzenia strony, więc jest niewidoczny w odróżnieniu od zwykłego systemu plików. Zapewnia dostęp do specjalnych funkcji który jest wysoce zoptymalizowany pod kątem wydajności i zapewnia dostęp do treści.
Poniżej znajdziesz na przykład informacje o tym, jak zapisać i przywrócić plik modelu w pliku 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;
}
};
Interfejs API IndexedDB
IndexedDB to uznany standard przechowywania dowolnych danych w trwały sposób. w przeglądarce. Znana jest ze złożoności interfejsu API, biblioteka opakowań, np. idb-keyval możesz traktować IndexedDB jak klasyczny magazyn par klucz-wartość.
Na przykład:
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;
}
};
Oznacz miejsce na dane jako przechowywane
Zadzwoń pod numer navigator.storage.persist()
na końcu dowolnej z tych metod buforowania, aby poprosić o zgodę na użycie
pamięci trwałej. Ta metoda zwraca obietnicę, która zwraca wartość true
, jeśli
zostanie przyznane uprawnienie. W przeciwnym razie false
. Przeglądarka
może zaakceptować prośbę,
w zależności od reguł właściwych dla danej przeglądarki.
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);
}
}
Przypadek specjalny: użycie modelu na dysku twardym
Możesz odwoływać się do modeli AI bezpośrednio z dysku twardego użytkownika do pamięci przeglądarki. Dzięki tej technice aplikacje badawcze mogą prezentować możliwość uruchomienia określonych modeli w przeglądarce lub umożliwienia wykonawcom użycia dzięki którym można samodzielnie wytrenować model w profesjonalnych aplikacjach pobudzających kreatywność.
File System Access API
Za pomocą interfejsu File System Access API można otwierać pliki zapisane na dysku twardym i uzyskać FileSystemFileHandle który można zachować w IndexedDB.
W przypadku tego wzorca użytkownik musi tylko przyznać dostęp do pliku modelu
raz. Dzięki trwałym uprawnieniom,
użytkownik może trwale przyznać dostęp do pliku. Po ponownym załadowaniu
aplikacji i wymagany gest użytkownika, taki jak kliknięcie myszy,
FileSystemFileHandle
można przywrócić z IndexedDB z dostępem do pliku
na dysku twardym.
Zapytania o uprawnienia dostępu do plików są wysyłane w razie potrzeby i w razie potrzeby wymagane, co sprawia, bez problemów przy doładowywaniu w przyszłości. Ten przykład pokazuje, jak uzyskać dla pliku z dysku twardego, a następnie przechowywać i przywracać uchwyt.
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;
}
};
Te metody nie wykluczają się wzajemnie. Może się zdarzyć, że oboje bezpośrednio buforować model w przeglądarce i używać go z dysku twardego użytkownika.
Prezentacja
Możesz zapoznać się ze wszystkimi 3 zwykłymi metodami przechowywania zgłoszeń oraz metodą dysku twardego zaimplementowane w wersji demonstracyjnej LLM MediaPipe.
Bonus: pobieraj duże pliki, partiami
Jeśli musisz pobrać duży model AI z internetu, pobrać do osobnych fragmentów, a następnie połączyć ponownie w kliencie.
Oto funkcja pomocnicza, której możesz użyć w kodzie. Wystarczy zdać
url
. chunkSize
(domyślnie: 5 MB), maxParallelRequests
(domyślnie: 6), funkcja progressCallback
(która raportuje
downloadedBytes
i łączna kwota fileSize
) oraz signal
w przypadku
Sygnał AbortSignal
jest opcjonalny.
Możesz skopiować poniższą funkcję w swoim projekcie lub
zainstaluj pakiet fetch-in-chunks
z pakietu 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;
Wybierz metodę odpowiednią dla siebie
W tym przewodniku przedstawiamy różne metody skutecznego buforowania modeli AI w przez przeglądarkę – to zadanie kluczowe dla zwiększania wygody użytkowników i wydajności aplikacji. Zespół Chrome ds. miejsca na dane zaleca interfejs Cache API dla optymalną wydajność, która zapewnia szybki dostęp do modeli AI, co skraca czas wczytywania i zwiększaniem czasu reagowania.
Opcje OPFS i IndexedDB są mniej przydatne. Interfejsy API OPFS i IndexedDB musisz zserializować dane, zanim będzie można je zapisać. IndexedDB wymaga też deserializacja danych podczas ich pobierania, co czyni je najgorszym miejscem do ich przechowywania dużych modeli.
Dla niszowych aplikacji interfejs File System Access API zapewnia bezpośredni dostęp do plików na urządzeniu użytkownika, co jest idealnym rozwiązaniem w przypadku użytkowników, którzy zarządzają własnymi modelami AI.
Jeśli chcesz zabezpieczyć swój model AI, zachowaj go na serwerze. Po zapisaniu na wyodrębnienie danych z pamięci podręcznej i IndexedDB jest proste – lub rozszerzenie OFPS DevNarzędzia. Te interfejsy API do przechowywania danych z natury mają jednakowe podejście w zakresie bezpieczeństwa. Może Cię kusić przechowywać zaszyfrowaną wersję modelu, ale później musisz je odszyfrować. klucz do klienta, który może zostać przechwycony. Oznacza to próbę podejrzenia, że nieuczciwy podmiot kradzież modelu jest nieco trudniejsza, ale nie niemożliwa.
Zachęcamy do wybrania strategii buforowania zgodnej z wymagań, zachowań docelowych odbiorców i cech modeli AI . Dzięki temu aplikacje są responsywne i niezawodne w różnych warunki sieciowe i ograniczenia systemu.
Podziękowania
Tę opinię ocenili Joshua Bell, Reilly Grant, Evan Stade, Nathan Memmott, Austin Sullivan, Etienne Noël, André Bandarra, Alexandra Klepper, François Beaufort, Paul Kinlan i Rachel Andrew.