Realays Logo Realays
← Back to Blog
Tech 1/14/2026

[Tech Series 04] Production-Level Web ML Optimization

Covering optimization techniques (quantization, memory management, etc.) to solve performance issues encountered when applying Web ML to actual services.

[Tech Series 04] Production-Level Web ML Optimization

Production-Level Web ML Optimization Techniques

Beyond simply implementing Web ML technology, achieving production-level service that satisfies actual users requires intense optimization. The browser environment has limited resources in terms of memory, computational power, and network bandwidth compared to high-performance servers.

1. Model Lightening: Quantization

The most effective optimization method is reducing the model size itself. AI model weights are stored as 32-bit floating-point (float32) by default. The technique of representing these as 16-bit (float16) or even 4-bit (int4) integers is called quantization.

Benefits of Quantization

Size Reduction:

  • Applying 4-bit quantization can reduce model size to 1/8 of the original
  • Dramatically shortens download time
  • Example: Llama-3-8B model reduced from 16GB to 2GB

Memory Savings:

  • Reduced VRAM usage allows large models to run on lower-spec laptops or mobile devices
  • Enables multi-model deployment in a single page

Performance Improvement:

  • Memory bandwidth bottlenecks are relieved, speeding up computation
  • Lower precision arithmetic can be faster on modern hardware

Quantization Techniques

Post-Training Quantization

import tensorflow as tf

# Simple quantization example
converter = tf.lite.TFLiteConverter.from_saved_model('model/')
converter.optimizations = [tf.lite.Optimize.DEFAULT]
quantized_model = converter.convert()

Quantization-Aware Training

Train the model while simulating quantization effects for better accuracy:

import tensorflow_model_optimization as tfmot

# Quantization-aware training
q_aware_model = tfmot.quantization.keras.quantize_model(base_model)
q_aware_model.compile(optimizer='adam', loss='categorical_crossentropy')
q_aware_model.fit(train_data, epochs=10)

Mixed Precision

Use different precision levels for different layers:

  • Critical layers: Keep at float32 for accuracy
  • Less sensitive layers: Quantize to int8 or int4

2. Memory Lifecycle Management

You cannot rely solely on JavaScript’s garbage collector. Especially when using WebGL or WebGPU backends, tensor objects occupy GPU memory.

Explicit Memory Management

// BAD: Memory leak
async function badInference() {
  for (let i = 0; i < 1000; i++) {
    const tensor = tf.zeros([1000, 1000]);
    // Tensor not disposed - memory leak!
  }
}

// GOOD: Proper disposal
async function goodInference() {
  for (let i = 0; i < 1000; i++) {
    const tensor = tf.zeros([1000, 1000]);
    // Use tensor...
    tensor.dispose(); // Explicitly free memory
  }
}

Using tf.tidy()

// Automatic cleanup with tf.tidy()
const result = tf.tidy(() => {
  const a = tf.tensor([1, 2, 3]);
  const b = tf.tensor([4, 5, 6]);
  const c = a.add(b);
  return c; // Only c is kept, a and b are disposed
});

// Later, dispose result when done
result.dispose();

Memory Monitoring

// Monitor memory usage
console.log("Num tensors:", tf.memory().numTensors);
console.log("Num bytes:", tf.memory().numBytes);

// Set memory growth limit
tf.env().set("WEBGL_DELETE_TEXTURE_THRESHOLD", 0);

Without proper memory management, browser tabs can crash due to GPU memory exhaustion.

3. Preventing Main Thread Blocking (Web Workers)

Performance Optimization

JavaScript operates on a single thread by default. If you perform heavy AI computations on the main thread, the browser cannot respond to user inputs like clicks or scrolls, causing the screen to freeze.

Web Worker Implementation

main.js:

// Create worker
const worker = new Worker("ml-worker.js");

// Send task to worker
worker.postMessage({
  type: "inference",
  imageData: imageData,
});

// Receive result
worker.onmessage = (event) => {
  const { type, result } = event.data;
  if (type === "inference-result") {
    displayResult(result);
  }
};

ml-worker.js:

// Load TensorFlow.js in worker
importScripts("https://cdn.jsdelivr.net/npm/@tensorflow/tfjs");

let model = null;

// Initialize model
async function loadModel() {
  model = await tf.loadLayersModel("/models/model.json");
  postMessage({ type: "model-loaded" });
}

// Handle messages
self.onmessage = async (event) => {
  const { type, imageData } = event.data;

  if (type === "inference") {
    const tensor = tf.browser.fromPixels(imageData);
    const predictions = await model.predict(tensor);
    const result = await predictions.data();

    tensor.dispose();
    predictions.dispose();

    postMessage({ type: "inference-result", result });
  }
};

loadModel();

Benefits of Web Workers

  • Responsive UI: Main thread remains free for user interactions
  • Parallel Processing: Multiple workers can handle multiple tasks
  • Better Performance: Prevent UI jank and freezing

4. Intelligent Caching

Models downloaded once should be permanently cached in the browser’s local storage using Cache API or IndexedDB.

Service Worker Caching

// service-worker.js
const CACHE_NAME = "ml-models-v1";
const MODEL_URLS = ["/models/model.json", "/models/weights.bin"];

// Install event: pre-cache models
self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll(MODEL_URLS);
    }),
  );
});

// Fetch event: serve from cache
self.addEventListener("fetch", (event) => {
  if (event.request.url.includes("/models/")) {
    event.respondWith(
      caches.match(event.request).then((response) => {
        return response || fetch(event.request);
      }),
    );
  }
});

IndexedDB for Model Storage

// Store model in IndexedDB
async function cacheModel(modelUrl) {
  // Save model to IndexedDB
  await tf.io.copyModel(modelUrl, "indexeddb://my-model");
}

// Load from cache
async function loadCachedModel() {
  try {
    const model = await tf.loadLayersModel("indexeddb://my-model");
    return model;
  } catch (e) {
    // Fallback to network
    const model = await tf.loadLayersModel("https://cdn.com/model.json");
    await cacheModel("https://cdn.com/model.json");
    return model;
  }
}

Version Management

Implement versioning to download only updated parts:

const MODEL_VERSION = "v2.1";
const CACHE_KEY = `model-${MODEL_VERSION}`;

async function loadModelWithVersioning() {
  const cached = localStorage.getItem(CACHE_KEY);

  if (cached) {
    return await tf.loadLayersModel(`indexeddb://${CACHE_KEY}`);
  } else {
    // Clear old versions
    await clearOldModelCache();

    // Download new version
    const model = await tf.loadLayersModel("/models/model.json");
    await model.save(`indexeddb://${CACHE_KEY}`);
    localStorage.setItem(CACHE_KEY, "cached");

    return model;
  }
}

5. Progressive Loading

For very large models, implement progressive loading:

async function progressivelyLoadModel() {
  // Load lightweight base model first
  const baseModel = await tf.loadLayersModel("/models/base.json");

  // Show UI immediately with base model
  enableUI(baseModel);

  // Load full model in background
  const fullModel = await tf.loadLayersModel("/models/full.json");

  // Upgrade to full model when ready
  upgradeModel(fullModel);
}

6. Batch Processing

Process multiple inputs together for better GPU utilization:

// INEFFICIENT: One at a time
for (const image of images) {
  const tensor = tf.browser.fromPixels(image);
  await model.predict(tensor);
  tensor.dispose();
}

// EFFICIENT: Batch processing
const batch = tf.stack(images.map((img) => tf.browser.fromPixels(img)));
const results = await model.predict(batch);
batch.dispose();
results.dispose();

7. Model Pruning

Remove unnecessary weights to reduce model size:

import tensorflow_model_optimization as tfmot

# Prune 50% of weights
pruning_params = {
    'pruning_schedule': tfmot.sparsity.keras.PolynomialDecay(
        initial_sparsity=0.0,
        final_sparsity=0.5,
        begin_step=0,
        end_step=1000
    )
}

model_for_pruning = tfmot.sparsity.keras.prune_low_magnitude(
    base_model,
    **pruning_params
)

Performance Benchmarks

Before Optimization

  • Model size: 87MB
  • Load time: 12s (3G network)
  • Inference: 450ms
  • Memory usage: 1.2GB

After Optimization

  • Model size: 11MB (quantization + pruning)
  • Load time: 1.5s (cached after first visit)
  • Inference: 85ms (WebGPU + batching)
  • Memory usage: 150MB

8x smaller, 5x faster!

Conclusion

Production-level Web ML optimization is essential for delivering great user experiences. Key techniques include:

  • ✅ Quantization to reduce model size
  • ✅ Explicit memory management with dispose()
  • ✅ Web Workers to keep UI responsive
  • ✅ Intelligent caching for instant loading
  • ✅ Progressive loading for large models
  • ✅ Batch processing for efficiency

By applying these techniques, you can make your Web ML applications production-ready and delightful to use.

Related Posts