A maioria dos modelos de IA tem pelo menos uma coisa em comum:
bastante grande para um recurso que é
transferidos pela Internet. O menor modelo de detecção de objetos do MediaPipe
(SSD MobileNetV2 float16
) pesa 5,6 MB
e o maior tem cerca de 25 MB.
O LLM de código aberto
gemma-2b-it-gpu-int4.bin
tem 1,35 GB, o que é considerado muito baixo para um LLM.
Os modelos de IA generativa podem ser enormes. É por isso que a IA é usada muito hoje em dia.
na nuvem. Cada vez mais, os apps executam modelos altamente otimizados diretamente
no dispositivo. Enquanto demonstrações de LLMs em execução no navegador
existem, aqui estão alguns exemplos no nível de produção de outros modelos em execução no
navegador:
- O Adobe Photoshop executa uma variante do modelo
Conv2D
no dispositivo para a ferramenta inteligente de seleção de objetos. - O Google Meet executa uma versão otimizada do modelo
MobileNetV3-small
para a segmentação de pessoas com o recurso de desfoque do plano de fundo. - A Tokopedia executa o modelo
MediaPipeFaceDetector-TFJS
para detecção facial em tempo real e evitar inscrições inválidas no serviço. - O Google Colab permite que os usuários usem modelos do disco rígido nos notebooks do Colab.
Para tornar os lançamentos futuros dos aplicativos mais rápidos, armazene explicitamente em cache os dados do modelo no dispositivo, em vez de depender do navegador HTTP implícito cache.
Embora este guia use gemma-2b-it-gpu-int4.bin model
para criar um chatbot,
a abordagem pode ser generalizada para se adequar a outros modelos e outros casos de uso
no dispositivo. A maneira mais comum de conectar um aplicativo a um modelo é disponibilizar o
com o restante dos recursos do app. É crucial otimizar
entrega.
Configurar os cabeçalhos de cache corretos
Se você veicula modelos de IA do seu servidor, é importante configurar
Cache-Control
cabeçalho. O exemplo a seguir mostra uma configuração padrão sólida, que você pode criar
para as necessidades do seu app.
Cache-Control: public, max-age=31536000, immutable
Cada versão lançada de um modelo de IA é um recurso estático. Conteúdo que nunca
as mudanças devem receber um tempo
max-age
combinado com impedimento de cache
no URL da solicitação. Se precisar atualizar o modelo,
dê um novo URL.
Quando o usuário recarrega a página, o cliente envia uma solicitação de revalidação, mesmo
embora o servidor saiba que o conteúdo é estável. A
immutable
indica explicitamente que a revalidação é desnecessária, pois a
do conteúdo não muda. A diretiva immutable
é
sem suporte
por navegadores e cache intermediário ou servidores proxy, mas também
combiná-lo com o
diretiva max-age
universalmente entendida, você garante o máximo
compatibilidade. O public
A diretiva de resposta indica que a resposta pode ser armazenada em um cache compartilhado.
Armazenar modelos de IA em cache do lado do cliente
Ao disponibilizar um modelo de IA, é importante armazená-lo explicitamente em cache navegador. Isso garante que os dados do modelo estejam prontamente disponíveis após a atualização do usuário o app.
Existem diversas técnicas para conseguir isso. Para os seguintes
das amostras de código, presuma que cada arquivo de modelo está armazenado em um
Objeto Blob
chamado blob
na memória.
Para entender o desempenho, cada exemplo de código é anotado com os
performance.mark()
e a performance.measure()
métodos. Essas medidas dependem do dispositivo e não podem ser generalizadas.
É possível usar uma das seguintes APIs para armazenar modelos de IA em cache no navegador: API Cache, a a API Origin Private File System API IndexedDB. A recomendação geral é usar API Cache, mas este guia discute as vantagens e desvantagens do todas as opções.
API Cache
A API Cache fornece
armazenamento permanente para Request
e o objeto Response
que são armazenados em cache
na memória de longa duração. Embora seja
definido na especificação Service Workers,
é possível usar essa API na linha de execução principal ou em um worker normal. Para usar fora de
contexto de um service worker, chame o método
Método Cache.put()
com um objeto Response
sintético, pareado com um URL sintético em vez de um
Request
.
Este guia pressupõe um blob
na memória. Usar um URL falso como chave de cache e uma
Response
sintético com base no blob
. Se você fizer o download direto
você usaria o Response
que receberia ao criar uma fetch()
solicitação.
Por exemplo, saiba como armazenar e restaurar um arquivo modelo com a API Cache.
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 Origin Private File System
O sistema de arquivos particular de origem (OPFS) é um padrão comparativamente jovem para endpoint de armazenamento do Google Cloud. É privado para a origem da página e, portanto, está invisível para o usuário, ao contrário do sistema de arquivos normal. Ele dá acesso a um serviço altamente otimizado para desempenho e que oferece acesso de gravação aos conteúdo.
Por exemplo, veja como armazenar e restaurar um arquivo de modelo no 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 é um padrão bem estabelecido para armazenar dados arbitrários de maneira persistente no navegador. É conhecido por sua API um tanto complexa, mas usando Uma biblioteca de wrapper, como idb-keyval é possível tratar o IndexedDB como um repositório clássico de chave-valor.
Exemplo:
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;
}
};
Marcar armazenamento como mantido
Chamar navigator.storage.persist()
ao final de qualquer um desses métodos de armazenamento em cache para solicitar permissão para usar
armazenamento permanente. Esse método retorna uma promessa que é resolvida como true
se
permissão seja concedida e false
caso contrário. O navegador
pode ou não atender à solicitação,
dependendo das regras específicas do navegador.
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);
}
}
Caso especial: usar um modelo em um disco rígido
Como alternativa, é possível referenciar modelos de IA diretamente no disco rígido do usuário ao armazenamento do navegador. Essa técnica pode ajudar os apps focados em pesquisa a mostrar a viabilidade de execução de certos modelos no navegador ou permitir que os artistas usem modelos autotreinados em apps especializados de criatividade.
API File System Access
Com a API File System Access, você pode abrir arquivos do disco rígido e obter uma FileSystemFileHandle que podem ser mantidas no IndexedDB.
Com esse padrão, o usuário só precisa conceder acesso ao arquivo do modelo
uma vez. Graças às permissões persistentes,
o usuário poderá optar por conceder acesso permanente ao arquivo. Depois de recarregar o
e um gesto do usuário necessário, como um clique do mouse, o
FileSystemFileHandle
pode ser restaurado do IndexedDB com acesso ao arquivo
no disco rígido.
As permissões de acesso aos arquivos são consultadas e solicitadas, se necessário, o que torna sem problemas para atualizações futuras. O exemplo a seguir mostra como receber para um arquivo do disco rígido e, em seguida, armazenar e restaurar o identificador.
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;
}
};
Esses métodos não são mutuamente exclusivos. Pode haver um caso em que vocês dois armazenar em cache explicitamente um modelo no navegador e usar um modelo do disco rígido do usuário.
Demonstração
É possível conferir os três métodos de armazenamento de casos regulares e o método do disco rígido implementados na demonstração do LLM do MediaPipe.
Bônus: faça o download de um arquivo grande em pedaços
Se você precisar fazer o download de um modelo grande de IA da Internet, carregue o fazer o download em partes separadas e juntar novamente no cliente.
Confira uma função auxiliar que pode ser usada no código. Você só precisa transmitir
para o url
. O chunkSize
(padrão: 5 MB), o maxParallelRequests
(padrão: 6), a função progressCallback
(que gera relatórios sobre
downloadedBytes
e o fileSize
total) e o signal
para um
O indicador AbortSignal
é opcional.
Você pode copiar a seguinte função no seu projeto ou
Instale o pacote fetch-in-chunks
pelo pacote npm (em inglês).
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;
Escolha o método ideal para você
Este guia explorou vários métodos para armazenar em cache modelos de IA de maneira eficaz no navegador, uma tarefa crucial para melhorar a experiência do usuário e a o desempenho do seu app. A equipe de armazenamento do Chrome recomenda a API Cache para desempenho ideal, para garantir acesso rápido aos modelos de IA, reduzindo o tempo de carregamento e melhorando a capacidade de resposta.
O OPFS e o IndexedDB são opções menos utilizáveis. As APIs OPFS e IndexedDB precisa serializar os dados antes de armazená-los. O IndexedDB também precisa desserializar os dados quando eles forem recuperados, tornando-os o pior lugar para armazenar modelos grandes.
Para aplicativos de nicho, a API File System Access oferece acesso direto a arquivos no dispositivo do usuário, ideal para usuários que gerenciam os próprios modelos de IA.
Se você precisar proteger seu modelo de IA, mantenha-o no servidor. Uma vez armazenados no cliente, é trivial extrair os dados do Cache e do IndexedDB com DevTools ou a extensão OFPS DevTools. Essas APIs de armazenamento são inerentemente iguais em segurança. Talvez você fique tentado a uma versão criptografada do modelo, mas depois você precisa receber a descriptografia para o cliente, que pode ser interceptada. Isso significa que uma pessoa mal-intencionada roubar seu modelo é um pouco mais difícil, mas não impossível.
Recomendamos que você escolha uma estratégia de armazenamento em cache compatível com a de IA, comportamento do público-alvo e características dos modelos de IA usados. Isso garante que seus aplicativos sejam responsivos e robustos em diversas condições da rede e restrições do sistema.
Agradecimentos
Isso foi revisado por Joshua Bell, Reilly Grant, Evan Stade, Nathan Memmott, Austin Sullivan, Etienne Noël, André Bandarra, Alexandra Klepper François Beaufort, Paul Kinlan e Rachel Andrew.