AI Hints
Home›MLOps Tutorial›Optimisation and Scale
MLOps · Lesson 37 of 80

Making Models Smaller

Quantization and compression.

⏱ 2 min read▶ Video at 1:32:10🎓 Free
Watch this lesson in the MLOps courseWatch on YouTube ↗Full MLOps course

A model that is too big, or too slow, costs money and annoys users. Sometimes you need it to run on a phone, or on a small server. The good news is that models are often much bigger than they need to be. In this lesson, you will shrink one, and check how much quality you lose, which is often, almost none.

Making Models Smaller
From the video: Making Models Smaller
  • Big models cost money
  • Shrink without losing much
  • Always check the quality

Four ways to shrink a model

There are four main techniques. Quantization stores the numbers with fewer bits, for example, eight bit integers, instead of thirty two bit decimals. Pruning removes weights, or whole neurons, that contribute little. Distillation trains a small student model, to imitate a large teacher, so you keep most of the quality, at a fraction of the size. And choosing a smaller architecture in the first place. Quantization is the easiest to try, because it needs no retraining.

Quantizationfewer bits per number
Pruningremove useless weights
Distillationsmall student copies big teacher
Smaller architectureright-size from the start

Quantizing a model

Here is quantization, on a real network. We train a neural network, with two hidden layers, and export it to O N N X, in the normal thirty two bit form. Then one call quantizes the weights to eight bit integers. We load both files, run the same test customers through each, and print the file size, and the accuracy. The compressed model is about a third of the size, and its accuracy is essentially identical. Smaller files load faster, use less memory, and often run faster, too.

quantize.pypython
import os
import numpy as np
import onnxruntime as ort
from onnxruntime.quantization import quantize_dynamic, QuantType
from skl2onnx import to_onnx
from sklearn.neural_network import MLPClassifier
from sklearn.metrics import accuracy_score
from churn import make_data

df = make_data(4000, seed=1)
X, y = df.drop(columns="churn").astype(np.float32), df["churn"]
model = MLPClassifier(hidden_layer_sizes=(128, 128), max_iter=300, random_state=0).fit(X[:3000], y[:3000])

with open("model_fp32.onnx", "wb") as f:
    f.write(to_onnx(model, X.to_numpy()[:1], options={"zipmap": False}).SerializeToString())
quantize_dynamic("model_fp32.onnx", "model_int8.onnx", weight_type=QuantType.QInt8)

for name in ("model_fp32.onnx", "model_int8.onnx"):
    session = ort.InferenceSession(name)
    labels = session.run(None, {session.get_inputs()[0].name: X.to_numpy()[3000:]})[0]
    size_kb = os.path.getsize(name) / 1024
    print(f"{name:16s} {size_kb:7.1f} KB   accuracy {accuracy_score(y[3000:], labels):.3f}")
Output
model_fp32.onnx     68.9 KB   accuracy 0.834
model_int8.onnx     21.5 KB   accuracy 0.836
Also uses churn.py
import numpy as np, pandas as pd

def make_data(n, seed, shifted=False):
    rng = np.random.default_rng(seed)
    tenure = rng.integers(1, 72, n)
    charges = rng.normal(65 + (28 if shifted else 0), 18, n).clip(20, 140).round(2)
    calls = rng.poisson(1.5 + (1.5 if shifted else 0), n)
    contract = rng.choice([0, 1, 2], n, p=[.55, .25, .20] if not shifted else [.30, .30, .40])
    logit = -1.0 - 0.04 * tenure + 0.025 * (charges - 65) + 0.45 * calls - 0.8 * contract
    if shifted:
        logit = -0.2 - 0.01 * tenure - 0.01 * (charges - 65) + 0.2 * calls - 0.2 * contract
    churn = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)
    return pd.DataFrame({"tenure": tenure, "charges": charges, "calls": calls, "contract": contract, "churn": churn})

The trade-offs of compression

Compression is not free, so be careful. Quantization may reduce accuracy a little, and sometimes a lot, for sensitive models. Pruning and distillation need extra training work. The savings depend on your hardware, because some chips run eight bit numbers much faster, and others do not. So follow a simple routine. Measure the baseline. Apply one technique. Measure quality, size, and speed again, on your own data. Keep the change, only if the trade is worth it.

Benefits

  • Smaller files
  • Less memory
  • Often faster inference
  • Lower serving cost

Costs and risks

  • A small accuracy loss
  • Extra engineering effort
  • Hardware dependent gains
  • Must be re-validated

Summary

Good. Quantization, pruning, and distillation shrink models, often with little loss. Always measure size, speed, and quality, on your own data, before and after. Next, getting more predictions per second, with batching and the right hardware.

Key takeaways
  • Quantize: fewer bits per weight
  • Prune, distill, right-size
  • Measure size, speed, quality
Next up: Batching and Hardware — more predictions per second