| name | chrome-developer |
| description | Expert Chrome developer skill covering advanced browser APIs and capabilities. Use this skill whenever working on Chrome extensions (Manifest V3), WebAssembly (Wasm 3.0), WebGPU (graphics and compute), WebNN (neural network inference), Chrome Built-in AI (Gemini Nano Prompt API, Summarizer, Translator), local ML model inference in the browser, IndexedDB (as SQL/NoSQL/vector/graph database), downloading and running HuggingFace models locally with WebGPU, or any advanced Chrome platform feature. Trigger this skill for requests about: building Chrome extensions, MV3 service workers, offscreen documents, declarativeNetRequest, running LLMs or ML models client-side (Qwen3, Llama, Phi, Gemma, Mistral, SmolLM), GPU compute shaders, WGSL shader language, WebAssembly SIMD/threads/GC, on-device AI without server infrastructure, browser performance optimization with WASM/GPU, WebGPU for machine learning, ONNX Runtime Web, Transformers.js v3, WebLLM, model quantization (q4/q4f16/fp16), IndexedDB as a database (key-value, relational patterns, vector similarity search, graph traversal, semantic RAG), caching ML models in IndexedDB/Cache API, or any task involving Chrome's advanced platform APIs. Also use for DevTools advanced usage, Chrome flags, performance profiling, and Chromium internals.
|
Chrome Developer — Advanced Platform Skill
You are an expert Chrome platform developer with deep knowledge of all advanced browser capabilities
as of 2026. You write production-quality code and give concrete, accurate guidance.
Capability Map
| Domain | Key APIs / Tools | Maturity |
|---|
| Chrome Extensions | MV3, Service Workers, Offscreen Documents, declarativeNetRequest | ✅ Stable |
| WebAssembly | Wasm 3.0, SIMD, Threads, GC, Memory64, Emscripten, wasm-bindgen | ✅ Stable |
| WebGPU | Compute Shaders, WGSL, Dawn, Render Bundles, Compatibility Mode | ✅ Stable (all browsers) |
| WebNN | MLContext, MLGraphBuilder, NPU/GPU/CPU routing | 🧪 Origin Trial (Chrome 146+) |
| Built-in AI | Gemini Nano, Prompt API, Summarizer, Writer, Translator, Rewriter | 🧪 Chrome 138+ (desktop) |
| Local ML Inference | ONNX Runtime Web, Transformers.js v3, WebLLM, TensorFlow.js | ✅ Stable |
| HF Models + WebGPU | Qwen3, Llama-3.2, Phi-4, Gemma-3, SmolLM2 — download & run | ✅ Stable |
| IndexedDB | NoSQL, SQL patterns, Vector DB, Graph DB, RAG store | ✅ Stable |
1. Chrome Extensions (Manifest V3)
Architecture Overview
MV3 extensions use a service worker as the background context (replaces persistent background pages).
Key constraints: no DOM access, terminates when idle, no global state persistence.
{
"manifest_version": 3,
"name": "My Extension",
"version": "1.0.0",
"permissions": ["storage", "alarms", "offscreen"],
"host_permissions": ["https://*/*"],
"background": {
"service_worker": "background.js",
"type": "module"
},
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"]
}],
"action": { "default_popup": "popup.html" }
}
Service Worker Patterns
State persistence — service workers terminate; always persist to chrome.storage:
let counter = 0;
async function increment() {
const { counter = 0 } = await chrome.storage.local.get('counter');
await chrome.storage.local.set({ counter: counter + 1 });
}
Keep-alive strategies (Chrome 116+):
- Active WebSocket connections reset the idle timer
- Messages from offscreen documents reset the timer
- Active
chrome.debugger sessions keep the worker alive
chrome.alarms minimum 30-second interval
Register listeners synchronously — async registration is not guaranteed:
chrome.storage.local.get(['ready'], () => {
chrome.runtime.onMessage.addListener(handler);
});
chrome.runtime.onMessage.addListener(handler);
chrome.alarms.onAlarm.addListener(alarmHandler);
Offscreen Documents
Use when you need DOM APIs inside an extension (audio, clipboard, canvas, workers, localStorage):
async function ensureOffscreen() {
const existing = await chrome.runtime.getContexts({
contextTypes: ['OFFSCREEN_DOCUMENT']
});
if (existing.length > 0) return;
await chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: ['DOM_SCRAPING'],
justification: 'Parse HTML content from page'
});
}
await ensureOffscreen();
chrome.runtime.sendMessage({ type: 'process', data: payload });
Available reasons: AUDIO_PLAYBACK, CLIPBOARD, DOM_PARSER, DOM_SCRAPING,
IFRAME_SCRIPTING, LOCAL_STORAGE, MATCH_MEDIA, WORKERS, GEOLOCATION, BATTERY_STATUS
Network Interception (declarativeNetRequest)
Replaces blocking webRequest. Performant, privacy-preserving:
[{
"id": 1,
"priority": 1,
"action": { "type": "block" },
"condition": {
"urlFilter": "||ads.example.com^",
"resourceTypes": ["script", "image"]
}
}]
await chrome.declarativeNetRequest.updateDynamicRules({
addRules: [{ id: 2, priority: 1, action: { type: 'redirect',
redirect: { url: 'https://safe.example.com' }}, condition: { ... }}],
removeRuleIds: [1]
});
Message Passing Patterns
const response = await chrome.runtime.sendMessage({ action: 'getData' });
const port = chrome.runtime.connect({ name: 'stream' });
port.onMessage.addListener(msg => console.log(msg));
port.postMessage({ type: 'start' });
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.action === 'getData') {
fetchData().then(sendResponse);
return true;
}
});
📖 See references/chrome-extensions-mv3.md for complete API reference, storage types, permissions guide, testing patterns, and Chrome Web Store publishing checklist.
2. WebAssembly (Wasm 3.0)
What's New in Wasm 3.0 (Standardized Sept 2025)
| Feature | Description | Use Case |
|---|
| GC | Browser-native garbage collection | Java, Kotlin, Dart, Python → Wasm |
| Memory64 | 64-bit address space (>4GB) | Large datasets, ML models |
| SIMD (relaxed) | Hardware-optimized vector ops | Signal processing, ML inference |
| Threads | SharedArrayBuffer + atomics | Parallel compute, workers |
| Tail Calls | Stack-safe recursion | Functional languages, interpreters |
| Exception Handling | Native try/catch with exnref | C++, Rust panic propagation |
| Multi-memory | Multiple independent linear memories | Isolation, modular design |
Toolchain Selection
Rust → wasm-pack / wasm-bindgen (best ergonomics, no GC needed)
C/C++ → Emscripten (most compatible, WebGPU via emdawnwebgpu)
Kotlin → KotlinJS / K2/Wasm (leverages browser GC)
Go → TinyGo or standard Go (tinygo better for size)
Python → Pyodide (ships CPython to browser)
Rust + wasm-bindgen Example
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn process_image(pixels: &[u8], width: u32, height: u32) -> Vec<u8> {
pixels.chunks(4)
.flat_map(|px| {
let gray = (0.299 * px[0] as f32 + 0.587 * px[1] as f32
+ 0.114 * px[2] as f32) as u8;
[gray, gray, gray, px[3]]
})
.collect()
}
wasm-pack build --target web --release
import init, { process_image } from './pkg/my_module.js';
const wasm = await init();
const result = process_image(pixelData, width, height);
Threading with SharedArrayBuffer
const worker = new Worker('worker.js');
const sharedBuffer = new SharedArrayBuffer(1024 * 1024);
const sharedArray = new Float32Array(sharedBuffer);
worker.postMessage({ buffer: sharedBuffer });
Performance Patterns
- Minimize JS↔Wasm boundary crossings — batch operations, pass typed arrays
- Use
WebAssembly.compileStreaming — compile in parallel with download
- Memory management — use
Memory64 for models >4GB, free explicitly in linear memory langs
- Feature detection — use
wasm-feature-detect library before relying on Wasm 3.0 features
📖 See references/webgpu-webnn-webassembly.md for WGSL shader patterns, Emscripten build flags, and WASM↔WebGPU interop.
3. WebGPU
Core Concepts
WebGPU is available in all major browsers (Chrome 113+, Firefox 141+, Safari 26+). It exposes
modern GPU capabilities: compute shaders, render pipelines, GPU buffers, and textures.
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const adapter = await navigator.gpu.requestAdapter({ featureLevel: 'compatibility' });
Compute Shader (WGSL) — ML-style tensor operation
// shader.wgsl
@group(0) @binding(0) var<storage, read> input: array<f32>;
@group(0) @binding(1) var<storage, read_write> output: array<f32>;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3u) {
let i = id.x;
if (i >= arrayLength(&input)) { return; }
// ReLU activation
output[i] = max(0.0, input[i]);
}
const pipeline = await device.createComputePipelineAsync({
layout: 'auto',
compute: { module: device.createShaderModule({ code: shaderWGSL }), entryPoint: 'main' }
});
const inputBuffer = device.createBuffer({
size: data.byteLength,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(inputBuffer, 0, data);
const outputBuffer = device.createBuffer({
size: data.byteLength,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
});
const bindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: { buffer: inputBuffer } },
{ binding: 1, resource: { buffer: outputBuffer } },
]
});
const encoder = device.createCommandEncoder();
const pass = encoder.beginComputePass();
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.dispatchWorkgroups(Math.ceil(data.length / 64));
pass.end();
device.queue.submit([encoder.finish()]);
WebGPU + ML Frameworks
import * as ort from 'onnxruntime-web';
ort.env.wasm.wasmPaths = '/wasm/';
const session = await ort.InferenceSession.create('./model.onnx', {
executionProviders: ['webgpu', 'wasm'],
});
const feeds = { input: new ort.Tensor('float32', inputData, [1, 3, 224, 224]) };
const results = await session.run(feeds);
import { pipeline } from '@huggingface/transformers';
const classifier = await pipeline('text-classification', 'Xenova/distilbert-base-uncased-finetuned-sst-2-english', {
device: 'webgpu',
});
const result = await classifier('I love this!');
📖 See references/webgpu-webnn-webassembly.md for render pipeline patterns, texture formats, and performance profiling.
4. WebNN (Web Neural Network API)
WebNN abstracts ML hardware (CPU/GPU/NPU) through a unified graph-based API. In Chrome 146+ origin trial.
if (!('ml' in navigator)) {
console.warn('WebNN not supported — fallback to ONNX/WebGPU');
}
const context = await navigator.ml.createContext({ deviceType: 'gpu' });
const builder = new MLGraphBuilder(context);
const inputDesc = { dataType: 'float32', dimensions: [1, 784] };
const weightsData = new Float32Array(784 * 128);
const biasData = new Float32Array(128);
const input = builder.input('input', inputDesc);
const weights = builder.constant({ dataType: 'float32', dimensions: [784, 128] }, weightsData);
const bias = builder.constant({ dataType: 'float32', dimensions: [128] }, biasData);
const matmul = builder.matmul(input, weights);
const biased = builder.add(matmul, bias);
const output = builder.relu(biased);
const graph = await builder.build({ output });
const inputTensor = new Float32Array(784);
const outputTensor = new Float32Array(128);
await context.compute(graph, { input: inputTensor }, { output: outputTensor });
Hardware routing: Windows uses DirectML, macOS uses Core ML, Linux uses vendor drivers.
Always feature-detect and degrade to WebGPU or Wasm.
📖 See references/webgpu-webnn-webassembly.md for complete MLGraphBuilder ops reference.
5. Chrome Built-in AI (Gemini Nano)
Available on Chrome 138+ desktop (Windows 10/11, macOS 13+, Linux). Requires 22GB free storage,
4GB+ VRAM or 16GB RAM.
Availability Check (always do this first)
const status = await LanguageModel.availability();
if (status === 'unavailable') {
}
if (status === 'downloadable') {
const session = await LanguageModel.create({
monitor(m) { m.addEventListener('downloadprogress', e => {
console.log(`${Math.round(e.loaded / e.total * 100)}%`);
}); }
});
}
Prompt API
const session = await LanguageModel.create({
systemPrompt: 'You are a helpful assistant that responds concisely.',
temperature: 0.7,
topK: 40,
});
const tokenCount = await session.countPromptTokens(userInput);
if (tokenCount > session.tokensLeft) {
}
const stream = session.promptStreaming(userInput);
for await (const chunk of stream) {
outputElement.textContent += chunk;
}
session.destroy();
Specialized APIs
const summarizer = await Summarizer.create({ type: 'key-points', length: 'short' });
const summary = await summarizer.summarize(longText);
const translator = await Translator.create({ sourceLanguage: 'en', targetLanguage: 'es' });
const translated = await translator.translate(text);
const writer = await Writer.create({ tone: 'professional', length: 'medium' });
const draft = await writer.write('Write a product announcement for...', { context: 'B2B SaaS' });
const rewriter = await Rewriter.create({ tone: 'casual' });
const rewritten = await rewriter.rewrite(formalText);
const detector = await LanguageDetector.create();
const results = await detector.detect(text);
Using Built-in AI in Extensions
const session = await LanguageModel.create({
systemPrompt: 'Extract structured data from web pages as JSON.'
});
const imageBlob = await fetch(imageUrl).then(r => r.blob());
const response = await session.prompt([
{ role: 'user', content: [
{ type: 'text', text: 'Describe this image:' },
{ type: 'image', image: imageBlob }
]}
]);
📖 See references/chrome-builtin-ai.md for full API surface, graceful degradation patterns,
and hybrid cloud+local strategies.
6. Local ML Model Inference — Framework Guide
Framework Selection Matrix
| Framework | Best For | Backend | Bundle Size |
|---|
| Transformers.js | NLP, vision, audio (HF models) | WebGPU/Wasm | ~2MB + model |
| ONNX Runtime Web | Any ONNX model, production | WebGPU/Wasm | ~5MB + model |
| WebLLM | Full LLM chat (Llama, Mistral) | WebGPU | ~10MB + model |
| TensorFlow.js | TF models, transfer learning | WebGPU/Wasm | ~3MB + model |
| MediaPipe | Vision tasks, hand/face tracking | Wasm/WebGL | ~1MB + model |
Model Loading Strategies
import { pipeline } from '@huggingface/transformers';
const model = await pipeline('text-classification', 'Xenova/bert-base-uncased', {
device: 'webgpu',
cache_dir: '/models/',
progress_callback: (progress) => {
console.log(`Loading: ${Math.round(progress.progress)}%`);
}
});
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/models/')) {
event.respondWith(caches.match(event.request).then(cached =>
cached || fetch(event.request).then(response => {
const clone = response.clone();
caches.open('ml-models-v1').then(c => c.put(event.request, clone));
return response;
})
));
}
});
7. Performance & Debugging
Chrome DevTools for Advanced APIs
chrome://gpu — GPU info, WebGPU/WebGL status
chrome://flags — Enable experimental APIs
chrome://on-device-internals — Gemini Nano model status
chrome://tracing — Deep performance profiling
Shift+Esc — Task Manager (per-tab GPU/memory)
DevTools → Application → IndexedDB — Browse IDB stores, clear data
DevTools → Application → Cache Storage — Inspect cached model shards
WebGPU Performance Profiling
const bundle = device.createRenderBundle(descriptor);
renderPass.executeBundles([bundle]);
const querySet = device.createQuerySet({ type: 'timestamp', count: 2 });
Extension Debugging
chrome://extensions/ — Load unpacked, inspect service worker
DevTools → Application → Service Workers
Background page console: inspect service worker directly
8. IndexedDB — Multi-Paradigm Browser Database
IndexedDB is the primary persistent storage for web apps and extensions. It supports storing
arbitrary JS values (including typed arrays for vectors), indexes for fast querying, and
transactions for atomicity. Quota is large (often GB-scale, bounded by disk).
Core API Patterns
function openDB(name, version, upgradeCallback) {
return new Promise((resolve, reject) => {
const req = indexedDB.open(name, version);
req.onupgradeneeded = (e) => upgradeCallback(e.target.result);
req.onsuccess = (e) => resolve(e.target.result);
req.onerror = (e) => reject(e.target.error);
});
}
const idbPut = (db, storeName, value, key) =>
new Promise((res, rej) => {
const tx = db.transaction(storeName, 'readwrite');
const req = tx.objectStore(storeName).put(value, key);
req.onsuccess = () => res(req.result);
req.onerror = () => rej(req.error);
});
Always prefer the idb wrapper library (npm install idb) — it wraps the callback API in clean promises and avoids common pitfalls.
IDB as NoSQL Document Store
import { openDB } from 'idb';
const db = await openDB('my-app', 1, {
upgrade(db) {
const store = db.createObjectStore('docs', { keyPath: 'id', autoIncrement: true });
store.createIndex('by-type', 'type');
store.createIndex('by-created', 'createdAt');
store.createIndex('by-tag', 'tags', { multiEntry: true });
}
});
await db.put('docs', { type: 'note', title: 'Hello', tags: ['work', 'ai'], createdAt: Date.now() });
const all = await db.getAll('docs');
const notes = await db.getAllFromIndex('docs', 'by-type', 'note');
const recent = await db.getAllFromIndex('docs', 'by-created', IDBKeyRange.lowerBound(Date.now() - 86400000));
IDB as SQL-style Relational Store
IndexedDB is key-value + indexes, so SQL JOIN patterns require manual implementation:
const db = await openDB('relational-demo', 1, {
upgrade(db) {
db.createObjectStore('users', { keyPath: 'id' });
const posts = db.createObjectStore('posts', { keyPath: 'id', autoIncrement: true });
posts.createIndex('by-user', 'userId');
posts.createIndex('by-user-date', ['userId', 'createdAt']);
}
});
async function getPostsByUser(userId) {
return db.getAllFromIndex('posts', 'by-user', userId);
}
async function getPostsInRange(userId, from, to) {
const range = IDBKeyRange.bound([userId, from], [userId, to]);
return db.getAllFromIndex('posts', 'by-user-date', range);
}
async function createUserWithPost(user, post) {
const tx = db.transaction(['users', 'posts'], 'readwrite');
await tx.objectStore('users').put(user);
await tx.objectStore('posts').put({ ...post, userId: user.id });
await tx.done;
}
async function countPostsByUser() {
const counts = {};
let cursor = await db.transaction('posts').store.index('by-user').openCursor();
while (cursor) {
counts[cursor.key] = (counts[cursor.key] || 0) + 1;
cursor = await cursor.continue();
}
return counts;
}
IDB as Vector Database (Semantic Search / RAG)
Store embeddings as Float32Array alongside documents. Cosine similarity computed in JS (or via WASM/WebGPU for scale).
const db = await openDB('vector-store', 1, {
upgrade(db) {
const store = db.createObjectStore('vectors', { keyPath: 'id', autoIncrement: true });
store.createIndex('by-collection', 'collection');
}
});
async function insertVector(collection, text, embedding, metadata = {}) {
return db.add('vectors', {
collection,
text,
embedding: Array.from(embedding),
metadata,
createdAt: Date.now()
});
}
function cosineSimilarity(a, b) {
let dot = 0, normA = 0, normB = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
}
async function similaritySearch(queryEmbedding, collection, topK = 5) {
const all = await db.getAllFromIndex('vectors', 'by-collection', collection);
const scored = all.map(doc => ({
...doc,
score: cosineSimilarity(queryEmbedding, doc.embedding)
}));
return scored
.sort((a, b) => b.score - a.score)
.slice(0, topK);
}
async function ragQuery(userQuestion, model, llmSession) {
const queryEmbedding = await generateEmbedding(model, userQuestion);
const hits = await similaritySearch(queryEmbedding, 'knowledge', 3);
const context = hits.map(h => h.text).join('\n\n');
return llmSession.prompt(`Context:\n${context}\n\nQuestion: ${userQuestion}`);
}
IDB as Graph Database
Model graphs as nodes + edges stores. Traverse with BFS/DFS using cursor-based lookups.
const db = await openDB('graph-store', 1, {
upgrade(db) {
db.createObjectStore('nodes', { keyPath: 'id' });
const edges = db.createObjectStore('edges', { keyPath: 'id', autoIncrement: true });
edges.createIndex('by-from', 'from');
edges.createIndex('by-to', 'to');
edges.createIndex('by-type', 'type');
}
});
const addNode = (id, data) => db.put('nodes', { id, ...data });
const addEdge = (from, to, type, data = {}) =>
db.add('edges', { from, to, type, ...data });
async function getNeighbors(nodeId, direction = 'out') {
const index = direction === 'out' ? 'by-from' : 'by-to';
const key = direction === 'out' ? 'to' : 'from';
const edges = await db.getAllFromIndex('edges', index, nodeId);
return Promise.all(edges.map(e => db.get('nodes', e[key])));
}
async function bfs(startId, maxDepth = 3) {
const visited = new Set([startId]);
const queue = [{ id: startId, depth: 0 }];
const result = [];
while (queue.length) {
const { id, depth } = queue.shift();
const node = await db.get('nodes', id);
result.push({ ...node, depth });
if (depth < maxDepth) {
const neighbors = await getNeighbors(id, 'out');
for (const n of neighbors) {
if (n && !visited.has(n.id)) {
visited.add(n.id);
queue.push({ id: n.id, depth: depth + 1 });
}
}
}
}
return result;
}
async function shortestPath(startId, endId) {
const dist = { [startId]: 0 };
const prev = {};
const queue = [startId];
while (queue.length) {
const current = queue.shift();
if (current === endId) break;
const edges = await db.getAllFromIndex('edges', 'by-from', current);
for (const edge of edges) {
const weight = edge.weight ?? 1;
const newDist = dist[current] + weight;
if (newDist < (dist[edge.to] ?? Infinity)) {
dist[edge.to] = newDist;
prev[edge.to] = current;
queue.push(edge.to);
}
}
}
const path = [];
let cur = endId;
while (cur) { path.unshift(cur); cur = prev[cur]; }
return { path, distance: dist[endId] };
}
IDB for ML Model Caching
Transformers.js and WebLLM automatically cache model shards in Cache API / IDB.
For manual model caching (e.g., custom ONNX files):
async function cacheModel(modelId, arrayBuffer) {
const db = await openDB('model-cache', 1, {
upgrade(db) { db.createObjectStore('models'); }
});
await db.put('models', arrayBuffer, modelId);
console.log(`Cached ${(arrayBuffer.byteLength / 1e6).toFixed(1)}MB model: ${modelId}`);
}
async function loadCachedModel(modelId) {
const db = await openDB('model-cache', 1);
return db.get('models', modelId);
}
async function getOrFetchModel(modelId, remoteUrl) {
const cached = await loadCachedModel(modelId);
if (cached) { console.log('Model loaded from IDB cache'); return cached; }
console.log('Downloading model...');
const resp = await fetch(remoteUrl);
const buf = await resp.arrayBuffer();
await cacheModel(modelId, buf);
return buf;
}
📖 See references/indexeddb-advanced.md for full IDB API, quota management, migration patterns, worker usage, and PGlite (full PostgreSQL in-browser) integration.
9. HuggingFace Models + WebGPU — Download & Run
When Chrome Built-in AI (Gemini Nano) is unavailable, download and run open models directly
via Transformers.js v3 (ONNX-based) or WebLLM (MLC-compiled). Both cache to IndexedDB/Cache API
after first download.
Model Selection Guide
| Model | VRAM (q4f16) | Use Case | Transformers.js ID |
|---|
| SmolLM2-135M | ~0.1GB | Ultra-light classification/chat | HuggingFaceTB/SmolLM2-135M-Instruct |
| SmolLM2-360M | ~0.3GB | Fast on-device chat | HuggingFaceTB/SmolLM2-360M-Instruct |
| Qwen3-0.6B | ~0.5GB | Reasoning, tool use, multilingual | onnx-community/Qwen3-0.6B-ONNX |
| SmolLM2-1.7B | ~1GB | Good balance quality/size | HuggingFaceTB/SmolLM2-1.7B-Instruct |
| Qwen3-1.7B | ~1.2GB | Coding, reasoning | onnx-community/Qwen3-1.7B-ONNX |
| Phi-4-mini (3.8B) | ~2.5GB | Strong reasoning, STEM | onnx-community/Phi-4-mini-instruct-ONNX |
| Qwen3-4B | ~2.8GB | Best quality <5GB VRAM | onnx-community/Qwen3-4B-ONNX |
| Llama-3.2-3B | ~2GB | General chat | onnx-community/Llama-3.2-3B-Instruct-ONNX |
| Gemma-3-4B | ~2.7GB | Multilingual, vision | via WebLLM |
| Qwen3-8B | ~5.5GB | Near-GPT-4 quality tasks | via WebLLM |
Rule of thumb: q4f16 = 4-bit weights + fp16 activations. Fits in ~0.6 × param_count GB. For a 4B model: ~2.4–3GB VRAM. Always prefer q4f16 over q4 on WebGPU for better accuracy.
Transformers.js v3 — Download & Run with WebGPU
import { pipeline, env, TextStreamer } from '@huggingface/transformers';
env.allowRemoteModels = true;
env.useBrowserCache = true;
env.backends.onnx.wasm.proxy = false;
const generator = await pipeline(
'text-generation',
'onnx-community/Qwen3-4B-ONNX',
{
device: 'webgpu',
dtype: 'q4f16',
progress_callback(p) {
if (p.status === 'downloading') {
const pct = Math.round(p.progress ?? 0);
console.log(`Downloading ${p.file}: ${pct}%`);
updateProgressUI(pct, p.file);
}
}
}
);
const messages = [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'Explain WebGPU in 2 sentences.' }
];
const streamer = new TextStreamer(generator.tokenizer, {
skip_prompt: true,
callback_function: (token) => outputEl.textContent += token,
});
const output = await generator(messages, {
max_new_tokens: 512,
do_sample: false,
streamer,
});
Run in a Web Worker (keeps UI responsive)
Always run model inference in a Worker — it prevents UI freezes during loading and inference.
import { pipeline, TextStreamer } from '@huggingface/transformers';
let generator = null;
self.onmessage = async ({ data }) => {
if (data.type === 'load') {
generator = await pipeline('text-generation', data.modelId, {
device: 'webgpu',
dtype: 'q4f16',
progress_callback: (p) => self.postMessage({ type: 'progress', progress: p }),
});
self.postMessage({ type: 'ready' });
}
if (data.type === 'generate') {
const streamer = new TextStreamer(generator.tokenizer, {
skip_prompt: true,
callback_function: (token) => self.postMessage({ type: 'token', token }),
});
const result = await generator(data.messages, {
max_new_tokens: data.maxTokens ?? 512,
streamer,
});
self.postMessage({ type: 'done', result });
}
};
const worker = new Worker(new URL('./worker.js', import.meta.url), { type: 'module' });
worker.postMessage({ type: 'load', modelId: 'onnx-community/Qwen3-4B-ONNX' });
worker.onmessage = ({ data }) => {
if (data.type === 'progress') updateUI(data.progress);
if (data.type === 'ready') enableChatInput();
if (data.type === 'token') appendToken(data.token);
};
WebLLM — Larger Models (Qwen3-8B+)
WebLLM is better for larger models (3B+). It uses MLC-compiled WebGPU kernels with
FlashAttention and PagedAttention for efficient KV cache management.
import { CreateMLCEngine } from '@mlc-ai/web-llm';
const WEBLLM_MODELS = {
fast_small: 'SmolLM2-1.7B-Instruct-q4f16_1-MLC',
balanced: 'Qwen3-4B-Instruct-q4f16_1-MLC',
quality: 'Qwen3-8B-Instruct-q4f16_1-MLC',
reasoning: 'Phi-4-mini-instruct-q4f16_1-MLC',
coding: 'Qwen2.5-Coder-7B-Instruct-q4f16_1-MLC',
};
const engine = await CreateMLCEngine(WEBLLM_MODELS.balanced, {
initProgressCallback: ({ progress, text }) => {
console.log(`[${Math.round(progress * 100)}%] ${text}`);
updateLoadingUI(progress);
},
});
const response = await engine.chat.completions.create({
messages: [
{ role: 'system', content: 'You are a coding assistant.' },
{ role: 'user', content: 'Write a binary search in JavaScript.' }
],
temperature: 0.7,
max_tokens: 1024,
stream: true,
});
for await (const chunk of response) {
const delta = chunk.choices[0]?.delta?.content || '';
outputEl.textContent += delta;
}
const jsonResponse = await engine.chat.completions.create({
messages: [{ role: 'user', content: 'List 3 fruits as JSON array' }],
response_format: { type: 'json_object' },
});
const data = JSON.parse(jsonResponse.choices[0].message.content);
Model Download + Cache Strategy
Models are large (0.5–6GB). Always handle caching properly:
async function isModelCached(modelId) {
const cacheName = `webllm/${modelId}`;
const cache = await caches.open(cacheName);
const keys = await cache.keys();
return keys.length > 0;
}
function estimateVRAM(paramBillions, dtype = 'q4f16') {
const multipliers = { 'fp32': 4, 'fp16': 2, 'q8': 1, 'q4f16': 0.6, 'q4': 0.5 };
return paramBillions * (multipliers[dtype] ?? 1);
}
async function canRunModel(paramBillions, dtype = 'q4f16') {
const adapter = await navigator.gpu?.requestAdapter();
if (!adapter) return false;
const needed = estimateVRAM(paramBillions, dtype) * 1e9;
return adapter.limits.maxBufferSize >= needed * 0.8;
}
async function selectBestModel() {
const adapter = await navigator.gpu?.requestAdapter();
if (!adapter) return null;
const maxBuf = adapter.limits.maxBufferSize / 1e9;
if (maxBuf >= 6) return 'onnx-community/Qwen3-8B-ONNX';
if (maxBuf >= 3) return 'onnx-community/Qwen3-4B-ONNX';
if (maxBuf >= 1.5) return 'onnx-community/Qwen3-1.7B-ONNX';
return 'onnx-community/Qwen3-0.6B-ONNX';
}
Embedding Models (for RAG / Vector Search)
const embedder = await pipeline(
'feature-extraction',
'Xenova/all-MiniLM-L6-v2',
{ device: 'webgpu', dtype: 'fp16' }
);
async function generateEmbedding(text) {
const output = await embedder(text, { pooling: 'mean', normalize: true });
return output.data;
}
async function batchEmbed(texts, batchSize = 32) {
const embeddings = [];
for (let i = 0; i < texts.length; i += batchSize) {
const batch = texts.slice(i, i + batchSize);
const outputs = await embedder(batch, { pooling: 'mean', normalize: true });
embeddings.push(...outputs.map(o => o.data));
}
return embeddings;
}
Converting Custom HuggingFace Models to ONNX
pip install optimum[exporters] transformers
python -m optimum.exporters.onnx --model Qwen/Qwen3-4B ./qwen3-4b-onnx/
python -m optimum.exporters.onnx \
--model Qwen/Qwen3-4B \
--task text-generation-with-past \
--fp16 \
--optimize O2 \
./qwen3-4b-onnx/
huggingface-cli upload your-username/Qwen3-4B-ONNX ./qwen3-4b-onnx/
📖 See references/hf-webgpu-models.md for complete model catalog, quantization guide,
Worker patterns, IndexedDB caching integration, and RAG pipeline examples.
Reference Files
Read these for deeper detail:
-
references/chrome-extensions-mv3.md — Complete MV3 API reference, permissions guide,
content security policy, storage comparison, native messaging, testing patterns, Chrome Web Store publishing
-
references/webgpu-webnn-webassembly.md — WebGPU render pipelines, WGSL language reference,
WebNN ops catalog, Wasm 3.0 feature flags, Emscripten build recipes, interop patterns
-
references/chrome-builtin-ai.md — Full Gemini Nano API surface, graceful degradation strategies,
hybrid cloud/local patterns, privacy considerations, token management, all built-in AI API examples
-
references/indexeddb-advanced.md — Complete IDB API, quota/migration, idb library, cursor patterns,
compound indexes, PGlite (PostgreSQL in-browser), HNSW approximate nearest-neighbor indexing
-
references/hf-webgpu-models.md — HuggingFace model catalog for browsers, dtype/quantization guide,
Transformers.js v3 & WebLLM advanced configuration, RAG pipelines, ONNX conversion recipes
Scripts
-
scripts/extension-boilerplate/ — Complete MV3 extension template with service worker,
offscreen document, content script, and popup
-
scripts/webgpu-compute-demo.js — Runnable WebGPU compute shader for tensor operations
with feature detection and fallbacks
-
scripts/local-inference-demo.js — Multi-backend ML inference with Built-in AI + Transformers.js +
ONNX Runtime Web + WebLLM graceful degradation chain
-
scripts/indexeddb-patterns.js — Full IDB pattern library: NoSQL store, SQL-style relational
patterns, vector similarity search, graph BFS/Dijkstra, model caching
-
scripts/hf-webgpu-inference.js — Complete HuggingFace + WebGPU pipeline: smart model selection,
Worker-based inference, streaming generation, RAG with IDB vector store