2026-05-15

Deploying Real-Time AI at the Edge: Architecture & Optimization

Practical strategies for deploying ML models to edge devices with strict latency and power constraints

Edge Computing
TensorRT
ONNX
Model Optimization
Inference

Deploying AI models on edge devices (robots, drones, IoT devices) is fundamentally different from cloud inference. You must respect latency budgets, power constraints, and hardware limitations.

The Edge Computing Challenge

Cloud vs Edge: The Trade-off Matrix

| Aspect | Cloud | Edge | |--------|-------|------| | Latency | 100-500ms | 10-50ms | | Power Budget | Unlimited | 5-50W | | Network | Always available | Intermittent | | Compute | GPUs/TPUs | CPU/Jetson/Qualcomm | | Use Case | Batch, analytics | Real-time, safety-critical |

Your autonomous robot doesn't have the luxury of cloud latency. When a person steps in front of it, you have 100ms to react. Cloud inference takes 500ms. Do the math.

Model Optimization Pipeline

Step 1: Quantization

Convert your FP32 model to INT8 (or lower):

 import torch
import torch.quantization as quantization

# Prepare model for quantization
model.qconfig = quantization.get_default_qat_qconfig('fbgemm')
quantization.prepare_qat(model, inplace=True)

# Calibrate on representative data
for batch in calibration_loader:
    model(batch)

# Convert to INT8
quantization.convert(model, inplace=True)
 

Results:

  • Model size: 75% reduction (256MB → 64MB)
  • Inference speed: 3-4x faster
  • Accuracy loss: typically 1-2%

Step 2: Pruning

Remove unimportant weights:

 from torch.nn.utils import prune

# Remove 50% of weights in conv layers
for module in model.modules():
    if isinstance(module, torch.nn.Conv2d):
        prune.l1_unstructured(module, name='weight', amount=0.5)
 

Pruning Strategies:

  • Magnitude-based: Remove small weights
  • Structured: Remove entire channels/filters
  • Iterative: Prune, train, repeat

Step 3: Knowledge Distillation

Train a small "student" model to mimic a large "teacher":

 def distillation_loss(student_output, teacher_output, temp=4.0):
    """
    KL divergence between student and teacher predictions
    Higher temperature = softer targets
    """
    student_probs = torch.nn.functional.softmax(student_output / temp, dim=1)
    teacher_probs = torch.nn.functional.softmax(teacher_output / temp, dim=1)
    
    return torch.nn.functional.kl_div(
        torch.log(student_probs),
        teacher_probs,
        reduction='batchmean'
    ) * (temp ** 2)
 

Quantization helps, but architecture matters most. MobileNet is 50x smaller than ResNet-50 with comparable accuracy. Choose efficient architectures from the start.

Inference Engine Selection

TensorRT (NVIDIA)

Best for NVIDIA Jetson devices:

 import tensorrt as trt
from torch2trt import torch2trt

# Convert PyTorch model to TensorRT
model_trt = torch2trt(model, [torch.randn(1, 3, 224, 224).cuda()])

# Inference
output = model_trt(input_tensor)
 

Advantages:

  • 5-10x speedup over PyTorch
  • Automatic kernel fusion
  • Mixed precision support

ONNX Runtime

Cross-platform inference:

 import onnxruntime as rt

# Load ONNX model
sess = rt.InferenceSession("model.onnx")

# Inference
input_name = sess.get_inputs()[0].name
output = sess.run(None, {input_name: input_data})
 

OpenVINO (Intel)

Optimized for Intel processors:

 # Convert model to OpenVINO format
mo --input_model model.onnx --output_dir ./model_ir
 

Memory-Efficient Inference

Pattern: Streaming/Sliding Window

For video processing, don't process the entire video in memory:

 def process_video_stream(video_path, model, window_size=30):
    """
    Process video in sliding windows to maintain constant memory
    """
    cap = cv2.VideoCapture(video_path)
    frame_buffer = deque(maxlen=window_size)
    
    while True:
        ret, frame = cap.read()
        if not ret:
            break
        
        frame_buffer.append(preprocess(frame))
        
        if len(frame_buffer) == window_size:
            # Process only the current window
            output = model(torch.stack(frame_buffer))
            yield output
 

Pattern: Batch Size = 1

Always use batch size 1 for real-time inference:

 # Don't do this:
batch_inputs = stack([frame1, frame2, frame3])  # Accumulation latency!

# Do this:
output = model(frame.unsqueeze(0))  # Immediate processing
 

Latency Profiling

Identify Bottlenecks

 import time
from collections import defaultdict

profiler = defaultdict(list)

def profile_function(name):
    def decorator(func):
        def wrapper(*args, **kwargs):
            start = time.perf_counter()
            result = func(*args, **kwargs)
            elapsed = (time.perf_counter() - start) * 1000  # ms
            profiler[name].append(elapsed)
            return result
        return wrapper
    return decorator

@profile_function("preprocessing")
def preprocess(frame):
    return cv2.resize(frame, (224, 224))

@profile_function("inference")
def infer(frame):
    return model(frame)

# After running:
for name, times in profiler.items():
    avg_ms = np.mean(times)
    p95_ms = np.percentile(times, 95)
    print(f"{name}: avg={avg_ms:.2f}ms, p95={p95_ms:.2f}ms")
 

Latency is non-deterministic. Always measure p95 and p99 percentiles, not just averages. An average of 30ms looks good until you hit a garbage collection pause and miss a deadline.

Power Optimization

Dynamic Frequency Scaling (DVFS)

 # Set GPU frequency (Jetson)
sudo jetson_clocks --show  # Check current settings
sudo nvpmodel -m 0         # Max performance mode
 

Precision Batching

Mix precisions for different layers:

 # Run detection at FP16 (faster)
# Run tracking at FP32 (more accurate)
with torch.cuda.amp.autocast(dtype=torch.float16):
    detections = detection_model(frame)

with torch.cuda.amp.autocast(dtype=torch.float32):
    tracks = tracking_model(detections)
 

Benchmark Results

Here's what you can expect with proper optimization:

ResNet-50 on Jetson Xavier (Throughput)

  • Original FP32: 45 fps
  • Quantized INT8: 180 fps (4x)
    • Pruning (30%): 220 fps (5x)
    • Distillation: 250 fps (5.5x)

By combining quantization, pruning, and knowledge distillation, you can achieve 5-6x speedup with <1% accuracy loss. That's the difference between 20ms and 4ms latency.

Deployment Checklist

  • [ ] Model quantized and validated
  • [ ] Latency profiled (p95 < budget)
  • [ ] Power consumption measured
  • [ ] Edge device tested (Jetson/Qualcomm)
  • [ ] Fallback strategy for inference failure
  • [ ] Model versioning system in place
  • [ ] Over-the-air update mechanism

Conclusion

Edge AI requires a different mindset than cloud ML. Start with model efficiency, measure relentlessly, and always have a safety fallback.

The future is distributed intelligence — AI at the edge, not in the cloud.


Resources

  • TensorRT Documentation: https://docs.nvidia.com/deeplearning/tensorrt/
  • ONNX Model Hub: https://github.com/onnx/models
  • Jetson Optimization Guide: https://docs.nvidia.com/jetson/