Inference Engineering: Systems, Scale & Edge Deployment

In modern AI engineering, training a foundation model represents less than 15% of the total lifecycle cost. The remaining 85% is consumed by inference serving—the operational compute required to execute millions of forward passes across production applications, robotics, smartphones, and edge devices.

Inference is not a simple function execution. It is a systems-level battle against hardware physical boundaries:

  1. The Prefill vs Decode Phase Dilemma: Processing the user prompt is compute-bound; generating subsequent tokens autoregressively is strictly memory-bandwidth bound.
  2. The KV Cache Memory Explosion: Storing past Key and Value activations for 4k-128k context windows rapidly exhausts GPU VRAM, causing memory fragmentation and limiting concurrent batch sizes.
  3. The Edge Constraint Envelope: Deploying vision and sensory models to microcontrollers (TinyML) where SRAM is capped at 256 KB, requiring zero dynamic memory allocation and INT8 quantization.

We engineer our inference systems across this complete continuum: from multi-GPU vLLM clusters down to bare-metal ARM Cortex-M microcontrollers.

Imagine you manage a high-speed express train station.

Prompt Processing (Prefill) is when 500 passengers arrive at once: you open all turnstiles in parallel, check tickets instantly, and fill the train cars to capacity (Compute-Bound).

Token Generation (Decode) is what happens after the train departs: the train must stop at 50 consecutive stations, and at every single station, only one person gets on. The train has to brake, open the doors, let one passenger step inside, and accelerate back to top speed 50 times in a row. It feels horribly inefficient because you are moving an entire 100-ton train for one person (Memory-Bandwidth-Bound).

Inference Engineering is the discipline of fixing this: packing multiple passenger streams together on the fly (Continuous Batching), guessing what stations people want in advance (Speculative Decoding), packing baggage tighter (Quantization), and building ultra-light electric scooters for field medics who have no train tracks (TinyML on Edge Microcontrollers).


1. The Roofline Model: Hardware Operational Regimes

Every piece of compute hardware—from an 80GB NVIDIA H100 GPU down to a $2 ESP32 microcontroller—operates under the mathematical boundaries of the Roofline Model:

Systems & Hardware Roofline Analysis
The Roofline Model & Arithmetic Intensity
Target Hardware
MEMORY-BANDWIDTH BOUND
0.5 FLOPs/B (Memory Bound)Knee: 591 FLOPs/B800 FLOPs/B (Compute Bound)
Knee (591 FLOPs/B)Memory Slope (3,350 GB/s)Peak Compute (1,979 TFLOPs)Operational Intensity (FLOPs / Byte, Log Scale)Attainable Perf (TFLOPs/s)
Attainable Perf
107.2 TFLOPs
5.4% of Peak Capacity
Limiting Bottleneck
Memory Bandwidth
Saturated at 3350 GB/s
Optimization Move
Quantize (INT8/FP8) or Batch Requests
Reduces bytes read per token
The Roofline Model determines whether your AI workload is Memory-Bandwidth-Bound or Compute-Bound. In LLM serving: Prompt Prefill has high arithmetic intensity (Compute-Bound), while Autoregressive Token Generation reads gigabytes of weights to produce 1 token (Memory-Bandwidth-Bound).

The Two Operational Regimes

The peak attainable throughput of a model on a given hardware accelerator is bounded by:

Attainable Performance (TFLOPs/s)=min⁡(Peak Compute,Operational Intensity×Memory Bandwidth)\text{Attainable Performance (TFLOPs/s)} = \min\left(\text{Peak Compute}, \text{Operational Intensity} \times \text{Memory Bandwidth}\right)

Where Operational Intensity (II) is defined as:

I=Total Floating Point Operations (FLOPs)Total Bytes Transferred from Memory (Bytes)I = \frac{\text{Total Floating Point Operations (FLOPs)}}{\text{Total Bytes Transferred from Memory (Bytes)}}

The Knee of the Roofline occurs at:

Iknee=Peak Compute Capacity (FLOPs/s)Peak Memory Bandwidth (Bytes/s)I_{\text{knee}} = \frac{\text{Peak Compute Capacity (FLOPs/s)}}{\text{Peak Memory Bandwidth (Bytes/s)}}

  • Compute-Bound Regime (I>IkneeI > I_{\text{knee}}): The arithmetic intensity is high enough that the execution units (ALUs / Tensor Cores) are saturated. Performance is limited by compute FLOPs.
  • Memory-Bandwidth-Bound Regime (I<IkneeI < I_{\text{knee}}): The execution units sit idle waiting for weights and activations to arrive across the memory bus. Performance is limited by memory bandwidth.
Predict First
Why does increasing the serving batch size (e.g. from batch size 1 to batch size 64) during autoregressive token decoding dramatically increase token throughput without linearly increasing latency?

2. KV Cache Optimization & PagedAttention

In modern transformer models, recomputing past keys and values at every token generation step scales quadratically: O(L2)O(L^2). To achieve linear generation time, models store past Key and Value projection vectors in a KV Cache.

However, the memory footprint of the KV Cache grows rapidly with sequence length:

KV Cache Size per Request (Bytes)=2×2×nlayers×nheads×dhead×L×Precision Bytes\text{KV Cache Size per Request (Bytes)} = 2 \times 2 \times n_{\text{layers}} \times n_{\text{heads}} \times d_{\text{head}} \times L \times \text{Precision Bytes}

PagedAttention vs Contiguous Allocation

In naive inference servers, memory for each request was allocated contiguously for the request's max_seq_len. This caused 60% to 80% of VRAM to be wasted due to over-reservation and memory fragmentation.

Explore how PagedAttention (vLLM) resolves memory fragmentation using virtual memory paging:

vLLM & High-Throughput Serving Architecture
PagedAttention vs Contiguous KV Cache Allocation
Memory Allocation Strategy
Active Concurrent Requests
3 of 3 Serving
Near-zero memory fragmentation
Memory Waste / Fragmentation
0% (Internal tail only)
Only partial last block is unused
Serving Throughput Gain
2.4x - 3.8x Speedup
Batches more requests into the same VRAM
Physical GPU KV Cache Memory (32 Blocks = 512 Tokens)Each cell = 1 Physical Block (16 Tokens)
B#0A
B#1C
B#2B
B#3FREE
B#4A
B#5C
B#6FREE
B#7B
B#8C
B#9FREE
B#10FREE
B#11A
B#12C
B#13FREE
B#14FREE
B#15FREE
B#16FREE
B#17FREE
B#18FREE
B#19FREE
B#20FREE
B#21FREE
B#22FREE
B#23FREE
B#24FREE
B#25FREE
B#26FREE
B#27FREE
B#28FREE
B#29FREE
B#30FREE
B#31FREE
Req A ("Translate code"):48 tokens
Req B ("Summarize paper"):32 tokens
Req C ("Write PyTorch test"):64 tokens
PagedAttention (vLLM, Kwon et al.) brings virtual memory paging to LLM serving. In traditional contiguous allocation, 60% to 80% of VRAM is wasted on over-reservation and external fragmentation. PagedAttention eliminates external fragmentation completely, enabling 2x to 4x higher serving throughput.

3. High-Throughput Serving: Continuous Batching & Speculative Decoding

Traditional batching (Static Batching) waits for all sequences in a batch to finish before accepting new requests. Because different sequences generate different lengths, shorter sequences sit idle (creating "inference bubbles") while waiting for the longest sequence.

Continuous Batching (iteration-level scheduling) evaluates scheduling after every single token generation step: finished sequences are evicted immediately and new requests are inserted into the running iteration.

Accelerating Generation: Speculative Decoding

Autoregressive token generation requires sequential execution. Speculative Decoding breaks this sequential dependency by pairing a small, fast Draft Model with a large Target Model:

Algorithmic Acceleration
Speculative Decoding: Draft-Verify Acceleration Engine
Workload Entropy
Draft Model: 8B parameters | Target Model: 70B parameters
Phase 1: Small Draft Model (Sequential)Fast Autoregressive
return
torch
.matmul
(
q
Generated 5 candidate tokens in 5 lightweight forward passes (~15ms).
Phase 2: Large Target Model (1 Parallel Pass)O(1) Verification
return✔
torch✔
.matmul → .bmm✗
(—
q—
Target model verified all tokens in ONE forward pass (~25ms). Accepted 2 tokens + sampled 1 correction token.
Net Tokens Emitted
3 Tokens
In a single target forward pass
Wall-Clock Speedup
1.88x Faster
Relative to naive autoregressive decoding
Mathematical Guarantee
Lossless Distribution Match
Modified rejection sampling preserves exact target logits
Speculative Decoding converts memory-bandwidth-bound token generation into compute-bound parallel verification. The draft model proposes K tokens sequentially, and the target model verifies all K tokens in parallel in a single forward pass, yielding 2x to 3x wall-clock speedup without any loss in output distribution.

The Speculative Decoding Guarantees

  1. Parallel Verification: The target model evaluates all KK draft tokens in a single parallel forward pass (O(1)O(1) verification).
  2. Exact Distributional Fidelity: Using modified rejection sampling, tokens are accepted with probability: αi=min⁡(1,Ptarget(xi)Pdraft(xi))\alpha_i = \min\left(1, \frac{P_{\text{target}}(x_i)}{P_{\text{draft}}(x_i)}\right) If a token is rejected, the target model resamples a replacement token from the normalized residual distribution: Presample(x)=relu(Ptarget(x)−Pdraft(x))P_{\text{resample}}(x) = \text{relu}\left(P_{\text{target}}(x) - P_{\text{draft}}(x)\right) This guarantees that the final output distribution is mathematically identical to sampling directly from the large target model!

4. Quantization: Compressing Precision for Datacenter & Edge

Quantization maps continuous 32-bit or 16-bit floating point numbers into discrete low-bit integer or floating representations (FP8, INT8, INT4):

Numerical Precision & Compression
Quantization Engine: FP32, FP16, FP8, INT8 & INT4
Target Precision
Range: [-6.0, +6.0]
Hardware Bit Representation
INT8 (Uniform Affine Quantization) (8 bits / parameter)
Format: 8-bit signed integer [-128, 127]
Fixed-point integer arithmetic: q = round(x / Scale) + ZeroPoint. 4x memory compression; runs on microcontrollers with no FPU.
Discretization Math & Reconstruction
Quantization Formula: q = round(x / Scale)
Scale factor ($S$): 0.04724
Quantized Integer ($q$): 81 (stored in 8 bits)
Reconstructed Float (x̂): 3.827
Reconstruction Error ($\epsilon$): 0.0230
70B Model VRAM Footprint
70 GB
Weights only (excluding KV Cache)
Hardware Infrastructure
1x 80GB H100 (Single GPU!)
Minimum serving cluster requirement
Memory Bandwidth Speedup
4x Throughput
Fewer bytes loaded across memory bus per token
Quantization transforms continuous floating point tensors into compact discrete representations. Converting FP16 to INT8 halves memory bandwidth pressure on GPUs, and running INT4 via AWQ enables 70B parameter models to run on single workstations and edge devices with less than 1% perplexity degradation.

Uniform Affine INT8 Quantization

The fundamental quantization mapping from real float x∈[α,β]x \in [\alpha, \beta] to an 8-bit integer q∈[−128,127]q \in [-128, 127] is:

q=clamp(⌊xS⌉+Z,−128,127)q = \text{clamp}\left(\left\lfloor \frac{x}{S} \right\rceil + Z, -128, 127\right)

Where the Scale factor (SS) and Zero-point (ZZ) are defined as:

S=β−αqmax⁡−qmin⁡=β−α255S = \frac{\beta - \alpha}{q_{\max} - q_{\min}} = \frac{\beta - \alpha}{255}

Z=round(−αS)+qmin⁡Z = \text{round}\left(\frac{-\alpha}{S}\right) + q_{\min}

De-quantization reconstructs the approximate floating-point value:

x^=S×(q−Z)\hat{x} = S \times (q - Z)

In Symmetric Quantization (used for zero-centered weights), Z=0Z = 0 and S=max⁡(∣x∣)127S = \frac{\max(|x|)}{127}, eliminating integer offset additions during matrix multiplications.


5. Inference on the Edge: TinyML & Microcontrollers

Moving inference to embedded microcontrollers (ARM Cortex-M0/M4/M7, ESP32, RISC-V) introduces an entirely different set of physical constraints:

  • Zero Operating System: Bare-metal C++ execution with no virtual memory or swap space.
  • Microsecond Memory Budget: Typically 64 KB to 256 KB of SRAM and 512 KB to 1 MB of Flash ROM.
  • Zero Dynamic Allocation: malloc and free lead to heap fragmentation and embedded Hard Fault crashes.

The Static Tensor Arena

Explore how TensorFlow Lite Micro structures memory execution inside embedded microcontrollers:

Edge AI & Microcontroller Architecture
TinyML Memory Architecture: Flash (ROM) vs SRAM Tensor Arena
Memory Planning Model
Flash Memory (ROM) — Read Only110 KB / 512 KB
Model weights, quantized INT8 constants, and operator kernels execute directly in place (XIP - eXecute In Place). Consumes 0 bytes of SRAM!
SRAM Working RAM — Tensor Arena48 KB / 128 KB
Activation tensors share planned buffer offsets. Layer 3 immediately reuses memory vacated by Layer 1.
Activation Memory Offsets during “Layer 2: DepthwiseConv”Peak Arena Budget: 48 KB
Offset 0..24 KBINACTIVE / REUSABLE
Offset 24..48 KBACTIVE LAYER OUTPUT
Layer Input: 24 KB | Output Tensor: 24 KB | Flash Weights: 16 KB
Memory Strategy
Zero Dynamic Malloc
Pre-allocated single uint8_t[] buffer
Fragmentation Risk
0% Fragmentation
ZERO (Deterministic Memory)
Target Platforms
ARM Cortex-M4 / ESP32 / RISC-V
Battery & solar-powered sensory edge nodes
TinyML runs on microcontrollers with milliwatt budgets and zero OS. Dynamic malloc/free causes fatal heap fragmentation on embedded systems. TensorFlow Lite Micro uses a static Tensor Arena: model weights execute directly from Flash (ROM), while intermediate layer activations overlap and reuse SRAM offsets with zero dynamic allocation.

The Three Invariants of Microcontroller Inference

  1. eXecute In Place (XIP): Quantized INT8 model weights reside permanently in non-volatile Flash memory (ROM) and are read directly, consuming zero bytes of precious SRAM.
  2. The Static Tensor Arena: A single static byte buffer (uint8_t tensor_arena[ARENA_SIZE]) is allocated at startup.
  3. Activation Lifetime Reuse: Intermediate tensor activations share memory offsets. Once Layer 1 finishes, Layer 3 reuses that identical physical SRAM address.

6. Live Interactive Inference Calculation in Python

Test this real, in-browser Python implementation to calculate the exact VRAM footprint, KV cache size, and memory bandwidth throughput for any model architecture:

Run it yourself
import math

def calculate_inference_profile(
params_billions: float = 70.0,
context_length: int = 4096,
batch_size: int = 16,
precision_bits: int = 16,
n_layers: int = 80,
n_kv_heads: int = 8,
d_head: int = 128,
gpu_bandwidth_gbs: float = 2039.0 # NVIDIA A100 HBM2e
):
bytes_per_param = precision_bits / 8.0
# 1. Static Model Weights Footprint
weights_vram_gb = (params_billions * 1e9 * bytes_per_param) / (1024**3)
# 2. KV Cache Footprint
# 2 (K & V) * layers * kv_heads * head_dim * precision_bytes * seq_len * batch
bytes_per_token_kv = 2 * n_layers * n_kv_heads * d_head * bytes_per_param
kv_cache_total_gb = (bytes_per_token_kv * context_length * batch_size) / (1024**3)
total_vram_gb = weights_vram_gb + kv_cache_total_gb
# 3. Memory Bandwidth Bounded Decode Speed (Batch Size = 1)
# Each token requires transferring all model weights across HBM
max_decode_tok_per_sec_b1 = (gpu_bandwidth_gbs * 1e9) / (params_billions * 1e9 * bytes_per_param)
return {
"weights_vram_gb": round(weights_vram_gb, 2),
"kv_cache_gb": round(kv_cache_total_gb, 2),
"total_vram_gb": round(total_vram_gb, 2),
"max_tok_per_sec_b1": round(max_decode_tok_per_sec_b1, 1),
}

profile = calculate_inference_profile(

Systems Architecture Takeaway

Inference is a systems-algorithm co-design problem. In the cloud, maximize batch size and arithmetic intensity with PagedAttention and speculative decoding. On the edge, eliminate dynamic allocation with static tensor arenas and compress model weights with symmetric INT8 quantization.

Explore the deep-dive chapters in this track:

Last updated Oct 30, 2026Edit this pageReport an issue