Model Compression Techniques

Deploy machine learning models efficiently on edge devices and in production. Covers quantization, pruning, knowledge distillation, and the patterns that reduce model size by 10x while retaining 95% accuracy.

Model compression is the process of reducing a machine learning model’s size, inference latency, and computational footprint while preserving its predictive accuracy. It matters when deploying models to edge devices, real-time services, or resource-constrained environments—such as mobile apps, IoT sensors, or serverless functions—where memory, bandwidth, and CPU are limited. A model that runs efficiently in a data center may fail in production due to slow response times or high memory usage; compression ensures it fits and performs.

Reducing Model Size with Quantization

Quantization converts floating-point weights and activations into lower-precision representations, typically 8-bit integers (int8), 16-bit floats (float16), or even 4-bit integers (int4). This reduces memory usage and speeds up computation, especially on hardware that supports integer arithmetic.

To quantize a model using the standard ONNX Runtime, run:

onnxruntime-quantizer \
  --model_path model.onnx \
  --quantized_model_path model_quantized.onnx \
  --quantization_approach QLinear \
  --calibration_method MinMax \
  --op_types_to_quantize "Conv,MatMul,Add" \
  --data_reader "CalibrationDataReader" \
  --input_data_dir ./calibration_data

The --calibration_method MinMax collects the min and max values across a representative dataset, which are then used to scale the original float32 values into the target range. The --op_types_to_quantize list determines which operators benefit most from quantization. For optimal results, ensure calibration data reflects real-world input distributions.

Failures often arise when the calibration dataset is too small or poorly sampled. The model may quantize well during training but degrade in inference due to mismatched dynamic ranges. A common silent failure is the absence of --input_data_dir, which causes the quantizer to use default dummy data, leading to inaccurate quantization parameters. The CalibrationDataReader must implement get_next() to return batches of input tensors, and its output must match the model’s input signature.

Accelerating Inference with Pruning

Pruning removes redundant weights—those with low magnitude—reducing the number of non-zero parameters. This leads to sparse models that can be stored efficiently and computed faster, particularly on hardware that supports sparse matrix multiplication.

Use the torch-prune library in PyTorch to perform magnitude-based pruning:

import torch
import torch.nn.utils.prune as prune

model = load_trained_model()


for name, module in model.named_modules():
    if isinstance(module, torch.nn.Conv2d):
        prune.l1_unstructured(module, name='weight', amount=0.5)
        prune.remove(module, 'weight')

# Save the pruned model
torch.save(model.state_dict(), 'model_pruned.pth')

The l1_unstructured function computes the L1 norm of each filter and removes the smallest 50% of weights. The prune.remove call converts the pruned parameters into a dense representation, enabling efficient storage and GPU inference.

A subtle failure occurs when pruning is applied too aggressively without retraining. The model’s accuracy drops because the pruned weights are not fine-tuned. The solution is to add a post-pruning fine-tuning loop:

model.train()
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)

for epoch in range(10):
    for inputs, labels in train_loader:
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

The training loop must use the same data pipeline as training, and the learning rate should be lower than in the initial training phase. The model often fails silently if model.train() is omitted—pruning affects only the forward pass, but backpropagation is disabled during fine-tuning.

Compressing Models with Knowledge Distillation

Knowledge distillation transfers knowledge from a large, accurate teacher model to a smaller student model. The student learns not just to predict labels but also to mimic the teacher’s output logits and intermediate representations.

Train the student model using the distilbert library with cross-entropy and KL divergence losses:

from transformers import DistilBertForSequenceClassification, TrainingArguments, Trainer

teacher_model = DistilBertForSequenceClassification.from_pretrained("distilbert-base-uncased")
student_model = DistilBertForSequenceClassification.from_pretrained("distilbert-base-uncased")

# Define training arguments
training_args = TrainingArguments(
    output_dir="./student_model",
    num_train_epochs=5,
    per_device_train_batch_size=16,
    learning_rate=2e-5,
    weight_decay=0.01,
    logging_dir="./logs",
    evaluation_strategy="steps",
    eval_steps=500,
)

# Define custom loss function
def loss_fn(student_logits, teacher_logits, labels, alpha=0.5, temperature=2.0):
    ce_loss = torch.nn.CrossEntropyLoss()(student_logits, labels)
    kl_loss = torch.nn.KLDivLoss(reduction="batchmean")(
        torch.log_softmax(student_logits / temperature, dim=1),
        torch.softmax(teacher_logits / temperature, dim=1)
    )
    return alpha * ce_loss + (1 - alpha) * kl_loss

# Train the student
trainer = Trainer(
    model=student_model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
    compute_metrics=compute_metrics,
    # Pass the custom loss function
    loss_fn=loss_fn,
)

trainer.train()

The temperature parameter controls how soft the teacher’s logits are: higher values produce smoother probability distributions, which help the student generalize better. The alpha parameter balances the contribution of cross-entropy and KL divergence losses.

A common failure is neglecting to align the teacher and student models’ input preprocessing. The teacher may use a different tokenizer, padding strategy, or sequence length than the student. The student model will perform poorly even with excellent distillation if the input data is not preprocessed identically. The training_args must include tokenizer and max_length settings that match the teacher’s.

Optimizing for Hardware-Specific Execution

Once a model is compressed, it must be optimized for the target hardware. This includes operator fusion, kernel selection, and layout transformations.

Use the tvm compiler to compile a model for an ARM-based edge device:

tvmc compile \
  --model-format onnx \
  --target "llvm -mtriple=aarch64-linux-gnu" \
  --output-format tvm \
  --output model.so \
  --pass-config "tir.disable_fusion=True" \
  --pass-config "relay.optimize=True" \
  --pass-config "relay.backend.use_llvm=True" \
  model.onnx

The --target flag specifies the target architecture, and --pass-config enables a sequence of compiler passes. The tir.disable_fusion pass prevents operator fusion, which is useful when debugging or when the target hardware benefits from explicit, separate kernels.

A frequent failure is the mismatch between the model’s input layout and the hardware’s preferred layout. For example, a model may expect NHWC (batch, height, width, channels) format, but the target device expects NCHW (batch, channels, height, width). The tvm compiler can transform the layout using relay.transform.LayoutTransform, but this must be explicitly applied before compilation:

import tvm.relay as relay

with relay.build_config(opt_level=3):
    mod, params = relay.frontend.from_onnx(model, shape_dict)
    mod = relay.transform.LayoutTransform(mod, {"conv2d": "NCHW"}, {"conv2d": "NHWC"})
    mod, params = relay.optimize(mod, params)

The layout transformation is not applied automatically by tvmc compile. Missing it results in suboptimal memory access patterns and slower inference. The opt_level=3 enables aggressive optimizations, but it can increase compilation time and memory usage during build.

Managing the Compression Pipeline

A complete model compression workflow integrates quantization, pruning, distillation, and compilation into a single pipeline. This ensures reproducibility and enables versioning.

Define a compression.yaml configuration file that governs the entire process:

compression_pipeline:
  - name: "pruning"
    type: "magnitude"
    parameters:
      layer_types: ["Conv2d", "Linear"]
      sparsity: 0.5
      retrain: true
      epochs: 10
      learning_rate: 1e-4
  - name: "distillation"
    type: "knowledge"
    parameters:
      teacher_model: "bert-large-uncased"
      temperature: 3.0
      alpha: 0.7
      loss: "ce_kl"
  - name: "quantization"
    type: "qlinear"
    parameters:
      data_reader: "CalibrationDataReader"
      calibration_method: "MinMax"
      op_types: ["Conv", "MatMul", "Add"]
      input_data_dir: "./calibration_data"
  - name: "compilation"
    type: "tvm"
    parameters:
      target: "llvm -mtriple=aarch64-linux-gnu"
      pass_config:
        - "tir.disable_fusion=True"
        - "relay.optimize=True"
      output_format: "tvm"
      output_filename: "model.so"

This file is consumed by a pipeline orchestrator such as prefect or airflow, or executed via a Makefile:

.PHONY: compress

compress: model_compressed.so
	@echo "Model pipeline completed successfully"

model_compressed.so: model.onnx compression.yaml
	tvmc compile \
	  --model-format onnx \
	  --target "llvm -mtriple=aarch64-linux-gnu" \
	  --output-format tvm \
	  --output model.so \
	  --pass-config "tir.disable_fusion=True" \
	  --pass-config "relay.optimize=True" \
	  model.onnx

	# Run the compression pipeline
	python run_compression_pipeline.py compression.yaml

The run_compression_pipeline.py script reads the YAML, applies each step in sequence, and logs the output. If any step fails, the pipeline stops, and the error is logged in a logs/ directory. A failure in the pruning step may leave behind a pruned_model.pth file, but the distillation step may use the original model instead of the pruned one—this mismatch is a frequent production failure.

Debugging Compression Failures

When a compressed model underperforms, the first step is to inspect the input and output tensors at each stage. Use tvmc to profile the model:

tvmc profile \
  --model model.so \
  --input-data "./input_data.json" \
  --output-profile profile.json \
  --repeat 10 \
  --number 5

The profile.json file contains execution times for each operator, memory usage, and CPU cycles. A high latency in a Conv2d layer may indicate that the kernel is not optimized for the input layout. A high memory usage in MatMul suggests the matrices are not packed efficiently.

Another key diagnostic is to compare the model’s outputs before and after compression. Use torch.testing.assert_allclose to validate the outputs of the original and compressed models:

import torch

original_outputs = model_original(inputs)
compressed_outputs = model_compressed(inputs)

torch.testing.assert_allclose(
    original_outputs,
    compressed_outputs,
    rtol=1e-3,
    atol=1e-4,
    msg="Model outputs differ after compression"
)

The rtol and atol values must be chosen based on the model’s quantization scheme. For int8 quantization, rtol=1e-3 is typical. A failure in this assertion often reveals a mismatch in preprocessing or a missing normalization step.

When a model fails in production, the first two questions are: “Did the model use the correct input format?” and “Are the quantization parameters correct?” The answer to both lies in the model’s metadata and the calibration data. The model must include the input_names, output_names, input_shapes, and quantization_params in its metadata. The calibration data must be versioned alongside the model and available for re-quantization.

Model compression is not a single step but a continuous process of tuning, validation, and iteration. The success of a compressed model depends not just on its final size or speed, but on the consistency and correctness of the data flow from training to deployment.

This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.