Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›Creating Own LLM›Optimizing Inference: ONNX, TensorRT, and Model Optimization Tools

Deployment and Advanced Topics

Optimizing Inference: ONNX, TensorRT, and Model Optimization Tools

This lesson covers how to accelerate large language model inference using ONNX, TensorRT, and other optimization tools. It matters because raw model performance is often too slow for production, and these tools bridge the gap between research and deployment. You reach for these techniques when latency, throughput, or hardware constraints demand better efficiency than native frameworks can provide.

Why Optimize Inference?

Optimizing inference is critical because large language models (LLMs) are computationally expensive, often requiring significant memory and processing power. Without optimization, even a well-trained model may be too slow for real-time applications like chatbots or API services. The core challenge is balancing accuracy with speed—optimizations like quantization, pruning, and hardware-specific acceleration reduce computational overhead while preserving model quality. For example, a model that takes 500ms per token in its native form might be reduced to 50ms with the right optimizations, making it viable for interactive use. These techniques are not just about speed; they also enable deployment on resource-constrained devices like edge hardware or mobile phones. Understanding the trade-offs—such as potential accuracy loss or increased complexity—helps you decide when and how to apply these tools. The goal is to make models faster without sacrificing their ability to generalize or handle diverse inputs.

# Example: Measuring baseline inference latency for a PyTorch model
import torch
import time

# Load a small pre-trained model (e.g., DistilGPT2) for demonstration
model = torch.hub.load('huggingface/pytorch-transformers', 'model', 'distilgpt2')
model.eval()

def measure_latency(model, input_ids, runs=100):
    # Warm-up runs to avoid initial overhead
    for _ in range(10):
        _ = model(input_ids)
    
    # Measure latency
    start_time = time.time()
    for _ in range(runs):
        _ = model(input_ids)
    end_time = time.time()
    
    avg_latency = (end_time - start_time) / runs
    return avg_latency

# Tokenize a sample input
input_text = "Optimizing inference is essential for deployment."
input_ids = torch.tensor([model.tokenizer.encode(input_text)])

latency = measure_latency(model, input_ids)
print(f"Average latency per inference: {latency:.4f} seconds")

Introduction to ONNX: The Universal Model Format

ONNX (Open Neural Network Exchange) is an open format designed to represent machine learning models in a framework-agnostic way. The key idea behind ONNX is to decouple the model from its training framework, allowing you to train in one environment (e.g., PyTorch) and deploy in another (e.g., TensorRT or ONNX Runtime). This interoperability is achieved by defining a standard set of operators and data types that all frameworks can map to. For LLMs, ONNX enables optimizations like operator fusion, where multiple operations (e.g., matrix multiplications) are combined into a single kernel, reducing memory bandwidth and compute overhead. Another advantage is hardware acceleration—ONNX Runtime can leverage CPU features like AVX-512 or GPU backends like CUDA without requiring framework-specific code. The trade-off is that not all framework features are supported, so you may need to simplify your model or rewrite custom layers to be ONNX-compatible. However, for most LLMs, the performance gains outweigh these limitations.

# Converting a PyTorch model to ONNX format
import torch.onnx

# Load a model and tokenizer (e.g., GPT-2)
model = torch.hub.load('huggingface/pytorch-transformers', 'model', 'gpt2')
model.eval()
tokenizer = model.tokenizer

# Define input shape (batch_size=1, sequence_length=10)
dummy_input = torch.tensor([[50256] * 10])  # Using padding token ID

# Export the model to ONNX
onnx_path = "gpt2.onnx"
torch.onnx.export(
    model,                      # Model to export
    dummy_input,                # Example input
    onnx_path,                  # Output file
    input_names=["input_ids"], # Input name for ONNX Runtime
    output_names=["logits"],   # Output name
    dynamic_axes={
        "input_ids": {0: "batch_size", 1: "sequence_length"},  # Dynamic axes
        "logits": {0: "batch_size", 1: "sequence_length"}
    },
    opset_version=11           # ONNX operator set version
)

print(f"Model exported to {onnx_path}")

Optimizing with ONNX Runtime

ONNX Runtime is a high-performance engine for executing ONNX models, designed to maximize inference speed across hardware platforms. The runtime works by analyzing the model graph and applying optimizations like operator fusion, constant folding, and memory layout transformations. For example, it can fuse a matrix multiplication followed by an addition into a single kernel, reducing memory access and improving cache utilization. ONNX Runtime also supports hardware-specific backends, such as CUDA for NVIDIA GPUs or DirectML for Windows devices, allowing you to leverage specialized hardware without changing your model. Another key feature is quantization-aware execution, where the runtime can run models quantized to 8-bit integers (INT8) with minimal accuracy loss, significantly reducing memory usage and compute requirements. To use ONNX Runtime, you first convert your model to ONNX format, then load it into the runtime and execute it with your input data. The runtime handles the rest, including memory management and kernel scheduling. The main limitation is that some advanced PyTorch features (e.g., dynamic control flow) may not be supported, so you may need to refactor your model to be compatible.

# Running an ONNX model with ONNX Runtime
import onnxruntime as ort
import numpy as np

# Load the ONNX model
sess_options = ort.SessionOptions()
sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
session = ort.InferenceSession("gpt2.onnx", sess_options)

# Prepare input (same as during export)
input_text = "Optimizing inference with ONNX Runtime"
input_ids = tokenizer.encode(input_text, return_tensors="np")

# Run inference
outputs = session.run(
    ["logits"],  # Output names
    {"input_ids": input_ids.astype(np.int64)}  # Input data
)

# Decode the output (simplified for demonstration)
predicted_token = np.argmax(outputs[0][:, -1, :], axis=-1)
predicted_text = tokenizer.decode(predicted_token[0])
print(f"Predicted next token: {predicted_text}")

TensorRT: NVIDIA's High-Performance Inference Engine

TensorRT is NVIDIA's SDK for high-performance deep learning inference, designed to maximize throughput and minimize latency on NVIDIA GPUs. Unlike ONNX Runtime, which is hardware-agnostic, TensorRT is deeply integrated with NVIDIA's GPU architecture, enabling optimizations like layer fusion, kernel auto-tuning, and precision calibration. For LLMs, TensorRT can significantly reduce inference time by leveraging GPU-specific features such as Tensor Cores, which accelerate mixed-precision operations (e.g., FP16 or INT8). The workflow involves converting your model to TensorRT's format, which involves parsing the model (e.g., from ONNX), building an optimized engine, and then executing it. During the build phase, TensorRT analyzes the model graph and selects the best kernels for your specific GPU, a process called kernel auto-tuning. It also performs layer fusion, where multiple layers are combined into a single operation to reduce memory bandwidth and improve cache efficiency. Another key feature is dynamic tensor shapes, which allows the engine to handle variable-length inputs without recompilation. The trade-off is that TensorRT is tied to NVIDIA GPUs, so it's not portable to other hardware. However, for deployments on NVIDIA GPUs, it often provides the best performance.

# Converting an ONNX model to TensorRT and running inference
import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit

# Logger for TensorRT
logger = trt.Logger(trt.Logger.WARNING)

# Build a TensorRT engine from an ONNX model
builder = trt.Builder(logger)
network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
parser = trt.OnnxParser(network, logger)

with open("gpt2.onnx", "rb") as model:
    parser.parse(model.read())

# Configure builder
config = builder.create_builder_config()
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 20)  # 1MB workspace

# Build engine (simplified for demonstration)
engine = builder.build_engine(network, config)

# Save engine for later use
with open("gpt2.engine", "wb") as f:
    f.write(engine.serialize())

print("TensorRT engine built and saved.")

Quantization and Pruning: Reducing Model Size and Compute

Quantization and pruning are two fundamental techniques for reducing the computational and memory footprint of LLMs. Quantization works by representing model weights and activations with lower-precision data types, such as 8-bit integers (INT8) instead of 32-bit floats (FP32). This reduces memory usage and speeds up computation, especially on hardware that supports low-precision arithmetic (e.g., NVIDIA Tensor Cores). The challenge is maintaining model accuracy—quantization can introduce noise, so techniques like quantization-aware training (QAT) or post-training quantization (PTQ) are used to minimize accuracy loss. Pruning, on the other hand, removes less important weights or neurons from the model, reducing its size and computational requirements. For example, you might prune weights with magnitudes below a certain threshold, effectively setting them to zero. The trade-off is that aggressive pruning can degrade model quality, so it's often combined with fine-tuning to recover accuracy. Both techniques are particularly useful for deploying LLMs on edge devices or in environments with strict latency requirements. When applying these optimizations, it's important to validate the model's performance on your specific task, as the impact on accuracy can vary depending on the model architecture and dataset.

# Post-training quantization with ONNX Runtime
import onnxruntime as ort
from onnxruntime.quantization import quantize_dynamic, QuantType

# Quantize the ONNX model to INT8
quantized_path = "gpt2_quantized.onnx"
quantize_dynamic(
    "gpt2.onnx",               # Input model
    quantized_path,             # Output model
    weight_type=QuantType.QUInt8  # Quantize weights to UINT8
)

# Compare model sizes
import os
original_size = os.path.getsize("gpt2.onnx")
quantized_size = os.path.getsize(quantized_path)
print(f"Original model size: {original_size / 1024:.2f} KB")
print(f"Quantized model size: {quantized_size / 1024:.2f} KB")
print(f"Size reduction: {100 * (1 - quantized_size / original_size):.2f}%")

# Run inference with the quantized model
session = ort.InferenceSession(quantized_path)
input_ids = tokenizer.encode("Quantization reduces model size", return_tensors="np")
outputs = session.run(["logits"], {"input_ids": input_ids.astype(np.int64)})
print("Quantized model inference completed.")

Key points

  • Optimizing inference is essential for deploying large language models in production, where latency and throughput are critical constraints.
  • ONNX provides a framework-agnostic format for models, enabling interoperability and hardware acceleration without tying you to a specific training framework.
  • ONNX Runtime applies graph-level optimizations like operator fusion and memory layout transformations to improve inference speed across diverse hardware.
  • TensorRT is NVIDIA's high-performance inference engine, offering GPU-specific optimizations like kernel auto-tuning and Tensor Core acceleration for minimal latency.
  • Quantization reduces model size and computational requirements by using lower-precision data types, such as INT8, while maintaining acceptable accuracy.
  • Pruning removes less important weights or neurons from a model, reducing its size and speeding up inference, but may require fine-tuning to recover accuracy.
  • The choice of optimization tool depends on your hardware, latency requirements, and tolerance for accuracy trade-offs in your specific use case.
  • Always validate the performance of optimized models on your target task, as the impact of techniques like quantization or pruning can vary significantly.

Common mistakes

  • Mistake: Skipping model quantization when optimizing for inference. Why it's wrong: Quantization reduces model size and speeds up inference by using lower-precision data types (e.g., FP16/INT8), but skipping it leaves performance gains on the table. Fix: Apply quantization (e.g., via TensorRT or ONNX Runtime) to balance accuracy and speed, especially for edge deployment.
  • Mistake: Assuming ONNX conversion preserves all model behaviors. Why it's wrong: ONNX may not support custom ops or dynamic control flow in your LLM, leading to silent failures or incorrect outputs. Fix: Validate the converted model thoroughly and use ONNX-compatible ops during training.
  • Mistake: Ignoring TensorRT’s layer fusion capabilities. Why it's wrong: TensorRT optimizes inference by fusing layers (e.g., convolution + activation), but manual tuning is often needed to maximize throughput. Fix: Profile the model with TensorRT’s profiler and adjust fusion strategies for your hardware.
  • Mistake: Over-optimizing for latency at the cost of accuracy. Why it's wrong: Aggressive optimizations (e.g., extreme quantization) can degrade LLM output quality, making responses incoherent or factually incorrect. Fix: Use accuracy-aware optimization tools and validate outputs against a benchmark dataset.
  • Mistake: Not leveraging hardware-specific optimizations. Why it's wrong: Tools like TensorRT are designed for NVIDIA GPUs, but generic ONNX Runtime may not exploit hardware features (e.g., Tensor Cores). Fix: Match optimization tools to your deployment hardware (e.g., TensorRT for NVIDIA, OpenVINO for Intel).

Interview questions

What is ONNX, and why would you use it when optimizing inference for your own LLM?

ONNX, or Open Neural Network Exchange, is an open standard format for representing machine learning models. It allows models trained in one framework, like PyTorch or TensorFlow, to be exported and run in another framework or runtime optimized for inference. When building your own LLM, you’d use ONNX because it decouples the training framework from the inference engine, enabling you to leverage specialized tools like ONNX Runtime for faster execution. For example, you can train a model in PyTorch, export it to ONNX, and then run it with hardware-specific optimizations without rewriting the model. This flexibility is critical for deploying LLMs efficiently across different platforms, from cloud servers to edge devices.

How does TensorRT improve inference performance for an LLM, and what are its key features?

TensorRT is NVIDIA’s high-performance deep learning inference library designed to optimize and accelerate model execution on NVIDIA GPUs. For an LLM, TensorRT improves inference performance through several key features: layer fusion, which combines multiple operations into a single kernel to reduce memory bandwidth and computation; precision calibration, which allows you to run models in lower precision (like FP16 or INT8) without significant accuracy loss; and kernel auto-tuning, which selects the best GPU kernels for your specific hardware. For example, you can convert an ONNX model to TensorRT using the `trtexec` tool, which applies these optimizations automatically. This results in lower latency and higher throughput, making it ideal for real-time LLM applications like chatbots or translation services.

Explain how quantization works in model optimization and why it’s important for LLMs.

Quantization is the process of reducing the precision of a model’s weights and activations, typically from 32-bit floating-point (FP32) to 16-bit (FP16) or even 8-bit integers (INT8). This reduces the model’s memory footprint and computational requirements, which is crucial for LLMs because they are often too large to run efficiently on standard hardware. For example, quantizing an LLM from FP32 to INT8 can reduce its size by 75% and speed up inference by 2-4x, with minimal impact on accuracy if done carefully. Tools like TensorRT or PyTorch’s quantization APIs can automate this process. Quantization is important for LLMs because it enables deployment on resource-constrained devices, like mobile phones or edge servers, while maintaining acceptable performance.

Compare ONNX Runtime and TensorRT for optimizing LLM inference. When would you choose one over the other?

ONNX Runtime and TensorRT are both powerful tools for optimizing LLM inference, but they serve different use cases. ONNX Runtime is a cross-platform inference engine that supports multiple hardware backends, including CPUs, GPUs, and even specialized accelerators like Intel’s OpenVINO. It’s ideal if you need flexibility across different hardware or if you’re not exclusively using NVIDIA GPUs. For example, you might use ONNX Runtime to deploy an LLM on a cloud instance with AMD GPUs or a CPU-only edge device. TensorRT, on the other hand, is NVIDIA-specific and offers deeper optimizations for NVIDIA GPUs, such as advanced layer fusion and precision calibration. You’d choose TensorRT when you’re deploying on NVIDIA hardware and need the absolute best performance, like in a high-throughput chatbot service. If portability is a priority, ONNX Runtime is the better choice; if raw speed on NVIDIA GPUs is the goal, TensorRT wins.

Describe the steps you would take to optimize an LLM for inference using TensorRT, including code snippets where relevant.

To optimize an LLM for inference using TensorRT, I’d follow these steps: First, export the model to ONNX format from your training framework, like PyTorch. For example, in PyTorch, you’d use `torch.onnx.export()` to convert the model. Next, use the `trtexec` tool or TensorRT’s Python API to convert the ONNX model to a TensorRT engine. Here’s a snippet for building the engine: `builder = trt.Builder(TRT_LOGGER); network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)); parser = trt.OnnxParser(network, TRT_LOGGER); parser.parse_from_file('model.onnx')`. During this step, you’d enable optimizations like FP16 or INT8 quantization. Then, calibrate the model if using INT8 to ensure accuracy isn’t compromised. Finally, serialize the engine to a `.plan` file and deploy it using TensorRT’s runtime. This process ensures the LLM runs with minimal latency and maximum throughput on NVIDIA GPUs.

How would you evaluate the trade-offs between model size, inference speed, and accuracy when optimizing an LLM for production? Provide a concrete example.

Evaluating the trade-offs between model size, inference speed, and accuracy requires a systematic approach. First, define your constraints: for example, if deploying on a mobile device, model size and inference speed are critical, while accuracy can be slightly relaxed. Start by measuring the baseline performance of your LLM in terms of latency, throughput, and accuracy on a validation set. Then, apply optimizations like quantization or pruning and re-evaluate. For instance, quantizing an LLM from FP32 to INT8 might reduce its size by 75% and double its inference speed, but you’d need to check if accuracy drops below an acceptable threshold, say 95% of the original. Use tools like TensorRT’s precision calibration to find the sweet spot. For example, if your LLM’s accuracy drops from 90% to 88% after quantization but latency improves from 500ms to 100ms, you might decide the trade-off is worth it for a real-time application like a voice assistant. Always validate with real-world data to ensure the optimized model meets your requirements.

All Creating Own LLM interview questions →

Check yourself

1. When optimizing an LLM for inference, why might you choose TensorRT over ONNX Runtime?

  • A.TensorRT supports more model architectures out of the box, including custom layers.
  • B.TensorRT is hardware-agnostic, while ONNX Runtime is tied to NVIDIA GPUs.
  • C.TensorRT performs layer fusion and kernel auto-tuning for NVIDIA GPUs, improving throughput.
  • D.ONNX Runtime requires manual quantization, while TensorRT handles it automatically.
Show answer

C. TensorRT performs layer fusion and kernel auto-tuning for NVIDIA GPUs, improving throughput.
Correct: TensorRT is optimized for NVIDIA GPUs, offering layer fusion (e.g., merging convolution and ReLU) and kernel auto-tuning to maximize performance. Incorrect: Option 1 is wrong because ONNX Runtime supports more architectures, not TensorRT. Option 2 is backwards (TensorRT is NVIDIA-specific). Option 4 is incorrect because both tools support automatic quantization.

2. Your LLM’s inference speed is slow after converting to ONNX. What is the most likely cause?

  • A.The model was trained with unsupported ops (e.g., dynamic control flow) that ONNX cannot optimize.
  • B.ONNX Runtime does not support GPU acceleration for LLMs.
  • C.The batch size was set too small during conversion, limiting parallelism.
  • D.ONNX models are inherently slower than native frameworks like PyTorch.
Show answer

A. The model was trained with unsupported ops (e.g., dynamic control flow) that ONNX cannot optimize.
Correct: ONNX may fail to optimize models with unsupported ops (e.g., dynamic loops), leading to suboptimal performance. Incorrect: Option 2 is false (ONNX Runtime supports GPUs). Option 3 is misleading (batch size affects throughput, not conversion). Option 4 is false (ONNX can match or exceed native performance).

3. How does quantization improve LLM inference, and what is a key trade-off?

  • A.It reduces memory usage by converting weights to lower precision (e.g., FP16), but may increase latency due to additional conversions.
  • B.It speeds up inference by using lower-precision data types (e.g., INT8), but can degrade output quality if applied too aggressively.
  • C.It enables dynamic batching, but requires manual tuning of the quantization range for each layer.
  • D.It eliminates the need for GPU acceleration, but only works with TensorRT.
Show answer

B. It speeds up inference by using lower-precision data types (e.g., INT8), but can degrade output quality if applied too aggressively.
Correct: Quantization (e.g., INT8) reduces memory bandwidth and compute requirements, speeding up inference, but aggressive quantization can hurt accuracy. Incorrect: Option 1 is wrong (quantization typically reduces latency). Option 3 is partially true but not the primary trade-off. Option 4 is false (quantization works with other tools and still benefits from GPUs).

4. You observe that your LLM’s outputs are less coherent after applying TensorRT optimizations. What should you investigate first?

  • A.Whether the model was trained with mixed precision (FP16/FP32), which TensorRT cannot handle.
  • B.Whether the quantization calibration dataset was representative of the LLM’s input distribution.
  • C.Whether TensorRT’s layer fusion altered the model’s attention mechanisms.
  • D.Whether the model’s vocabulary size was reduced during conversion.
Show answer

B. Whether the quantization calibration dataset was representative of the LLM’s input distribution.
Correct: Poor quantization calibration (e.g., using a non-representative dataset) can degrade accuracy. Incorrect: Option 1 is false (TensorRT supports mixed precision). Option 3 is unlikely (layer fusion doesn’t modify attention logic). Option 4 is irrelevant (vocabulary size isn’t affected by TensorRT).

5. Why might you use ONNX Runtime instead of TensorRT for deploying an LLM?

  • A.ONNX Runtime supports more hardware platforms (e.g., CPUs, AMD GPUs) and frameworks (e.g., PyTorch, TensorFlow).
  • B.ONNX Runtime automatically fuses layers, while TensorRT requires manual tuning.
  • C.TensorRT only works with NVIDIA GPUs, while ONNX Runtime is limited to CPUs.
  • D.ONNX Runtime guarantees better accuracy than TensorRT for all models.
Show answer

A. ONNX Runtime supports more hardware platforms (e.g., CPUs, AMD GPUs) and frameworks (e.g., PyTorch, TensorFlow).
Correct: ONNX Runtime is hardware-agnostic and supports more frameworks, making it versatile for cross-platform deployment. Incorrect: Option 2 is backwards (TensorRT handles fusion automatically). Option 3 is false (ONNX Runtime supports GPUs). Option 4 is false (accuracy depends on optimization settings, not the tool itself).

Take the full Creating Own LLM quiz →

← PreviousServing LLMs: APIs, Microservices, and Scalable InferenceNext →Building a User Interface for Your LLM: Gradio, Streamlit, or Custom Frontend

Creating Own LLM

36 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app