Edge Inference Targets
Pick an edge target and a quantization format. Bandwidth ceilings, formats, export steps and on-device measurement.
- Concepts and terms
- Tasks and commands
- Annotated code
- Diagrams
- Comparisons
- Troubleshooting
- Questions and tips
Four edge targets side by side#
| Factor | Apple ANE | Qualcomm Hexagon | WebGPU / WebLLM | NVIDIA Jetson |
|---|---|---|---|---|
| Peak | 38 TOPS, M4 / A18 | 45 TOPS, X Elite / 8 Gen 4 | Browser GPU, no install | Orin Nano Super, AGX Orin, Thor |
| Memory | Unified, no copy | Separate memory domain | Browser penalty ~25% | Memory-bound on Nano |
| Access | Core ML or MPS | QNN SDK, AI Hub | WebGPU compute shaders | vLLM, TensorRT Edge-LLM |
| Format | INT4 weights + FP16 activations | QNN INT8 / INT4 | Q4 MLC (q4f16_1) | Q4 GGUF, TRT-LLM INT4, NVFP4 |
| Observed | 8 tok/s estimate, A18, 8B Q4 | 7 tok/s, 8 Gen 3, 8B Q4 | ~41 tok/s, M3 Max, 8B Q4 | ~40 tok/s, AGX Orin, gpt-oss-20b |
| Limit | Native ANE needs Core ML | Convert from PyTorch or ONNX | ~70-75% mobile coverage | Nano fits 3B models |
Use each target when#
- Apple ANE
- an iOS app. Convert with Core ML. INT4 weights plus FP16 activations.
- Qualcomm Hexagon
- Snapdragon phones and Windows ARM laptops. Convert with QNN or AI Hub.
- WebGPU / WebLLM
- a browser app with no install. Keep a server fallback on the same OpenAI-compatible API.
- Jetson Orin Nano Super
- Llama 3.2 3B or Phi-3 on 8GB.
- Jetson AGX / Thor
- larger models, NVFP4 plus FP8 KV, EAGLE-3.
- GGUF Q4_K_M
- CPU and Apple Silicon laptops.
- GPTQ or AWQ INT4
- GPU inference with CUDA or ROCm.
Decode ceiling#
def ceiling(target: Target, model_gb: float) -> float: seconds_per_token = model_gb / target.bandwidth_gb_sall weights per token return 1 / seconds_per_tokentok/s upper boundmodel_gb = 4.7Llama 3.1 8B Q4Target("Apple A18 (iPhone 16)", 60, 8, "phone with ANE")GB/s, observed tok/sTarget("Snapdragon 8 Gen 3", 77, 7, "mid/high Android")Target("WebGPU on M3 Max", 400, 41, "browser penalty ~25%")Target("Jetson AGX Orin", 205, 45, "edge-datacenter bridge")Bandwidth math#
- Rule
- decode reads the full set of weights for every token.
- 7B Q4
- 3.5 GB. At 50 GB/s one token takes 70 ms, ~14 tok/s.
- High-end mobile
- 90 GB/s gives ~25 tok/s.
- HBM3 at 3 TB/s
- 1.2 ms per token, ceiling 830 tok/s.
- Gap
- 30-50x between datacenter and mobile DRAM.
- Long context
- 4 GB model plus 2 GB KV at 32K on an 8 GB phone is OOM.
- Context on edge
- keep 4K-8K unless you accept Q4 KV.
Quantization formats, Llama 3 70B#
| Factor | FP16 | FP8 | AWQ INT4 | GGUF Q4_K_M |
|---|---|---|---|---|
| Size | 140GB | 70GB | 35GB | 40GB |
| Perplexity | 3.12 | 3.14 | 4.18 | 4.25 |
| MMLU | 79.5% | 79.3% | 78.1% | 77.9% |
| Tokens/sec | 38, A100 | 55, A100 | 75, A100 | 28, CPU |
| Best for | Baseline | H100 deploys | GPU inference | CPU, Metal |
Quantize and dequantize#
scale = max(abs(tensor)) / 127symmetric INT8quantized = clamp(round(tensor / scale), -128, 127)reconstructed = quantized * scaleerror at most scale / 2quantized = round(tensor / scale) + zero_pointasymmetric, for ReLU outputsTasks#
| Task | Code |
|---|---|
| Edge decode ceilings, lesson 12 | code/ |
| Measure p50, p95, p99 on the device | measure_latency(model, |
| Time GPU work correctly | torch.cuda.synchronize() before and after the timed block |
| Static INT8 PTQ config | model.qconfig = |
| Export to ONNX | torch.onnx.export(model, |
| GPTQ INT4 | GPTQConfig(bits= |
| GGUF Q4_K_M | llama.cpp/ |
| WebLLM weights | mlc_llm convert_weight plus a compiled .wasm |
Vision deployment path#
Troubleshooting#
| Symptom | Cause | Fix |
|---|---|---|
| 8B Q4 at 3 tok/s on iPhone 16 Pro | Mobile bandwidth gap | Smaller model or fewer bits |
| OOM at long context on a phone | KV cache for 32K is 2 GB | Keep context at 4K-8K or use Q4 KV |
| WebLLM rejects a GGUF file | GGUF is not supported | Convert to Q4 MLC (q4f16_1) |
| llama.cpp on iOS skips the ANE | Metal backend uses MPS | Convert with Core ML |
| No WebGPU on an old Android browser | Needs Chrome v121+ | Fall back to the server API |
| First latency numbers too high | Cold caches and JIT | Warm up with 5-10 dummy passes |
| Naive INT4 output is garbage | Rounding errors accumulate | GPTQ or AWQ with calibration data |
| Perplexity delta above 2.0 | Something went wrong | Recheck. Under 0.5 is excellent |
Format glossary#
- BF16
- 8 exponent bits, 7 mantissa bits. Same range as FP32.
- FP8 E4M3
- weights and activations for inference.
- FP8 E5M2
- gradients in training. More range.
- INT8
- 256 values from -128 to 127. Needs a scale factor.
- INT4
- 16 levels. Needs GPTQ or AWQ for quality.
- GGUF
- llama.cpp file with mixed precision layers.
- PTQ
- quantize after training. 128-1024 calibration examples.
- QAT
- fake quantization in training. Better at INT4 and INT2.
Q&A#
- Why is compute secondary on edge?
- Decode is memory-bound. Bandwidth sets the ceiling.
- How fast is WebLLM against native?
- About 70-80% of native on the same backend.
- FLOPs or latency?
- FLOPs for architecture search. On-device latency for deployment.
- PTQ or QAT for vision?
- PTQ static gives 95% of the benefit with 5% of the effort.
- Is FP8 safe?
- Yes on H100. 30-50% faster with under 0.1% quality loss.