The Hitchhiker's Guide — Phase 06: Qualcomm NPU Hardware & Deployment
"Understanding Snapdragon is not optional for this role. It is the product." — Qualcomm Engineering
Section 1: Snapdragon SoC Architecture
The Snapdragon 8 Gen 3 (SM8650) is a heterogeneous SoC with multiple compute units:
┌─────────────────────────────────────────────────────────────┐
│ Snapdragon 8 Gen 3 │
│ │
│ ┌──────────────────┐ ┌──────────────────────────────┐ │
│ │ CPU (Kryo) │ │ GPU (Adreno 750) │ │
│ │ 1×Cortex-X4 │ │ Vulkan/OpenCL compute │ │
│ │ 5×Cortex-A720 │ │ ~3.8 TFLOPS FP16 │ │
│ │ 2×Cortex-A520 │ └──────────────────────────────┘ │
│ └──────────────────┘ │
│ ┌──────────────────────────────┐ │
│ ┌──────────────────┐ │ HTP (Hexagon Tensor Proc.) │ │
│ │ DSP (Hexagon) │ │ ~73 TOPS INT8 │ │
│ │ HVX (512-bit │ │ VTCM: 8 MB on-chip SRAM │ │
│ │ vector unit) │ │ HMX (Hexagon Matrix Ext.) │ │
│ └──────────────────┘ └──────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ DRAM (LPDDR5X @ 77 GB/s, 12 GB shared) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
HTP (Hexagon Tensor Processor) Deep Dive
The HTP is the AI accelerator on Snapdragon. Its key components:
HMX (Hexagon Matrix Extension): matrix multiply unit; processes INT8 8×8 matrix blocks in a single cycle; achieves ~73 TOPS INT8 throughput.
HVX (Hexagon Vector Extension): 512-bit SIMD vector unit; 64× INT8 multiplies per cycle; used for elementwise ops, activations, normalization.
VTCM (Vector Tightly Coupled Memory): 8 MB on-chip SRAM connected directly to HMX/HVX datapaths; bandwidth: ~2 TB/s (vs DRAM's 77 GB/s); critical: weights that fit in VTCM during a layer's computation avoid the 26× DRAM bandwidth penalty.
Tiling for VTCM: the QNN compiler tiles large weight matrices to fit in VTCM. For example, a 4096×4096 INT8 matrix = 16 MB — doesn't fit in 8 MB VTCM. The compiler tiles into 4 strips of 4096×1024 = 4 MB each, processes sequentially, accumulates output.
Power states: HTP has multiple power levels (turbo, nominal, SVS, SVS2). At SVS, throughput drops to ~20 TOPS but power consumption drops by 5×. The QNN runtime can switch states dynamically.
Section 2: SNPE vs QNN — When to Use Which
| Feature | SNPE (Snapdragon NPE SDK) | QNN (Qualcomm Neural Network SDK) |
|---|---|---|
| Generation | 2nd gen | 3rd gen (current) |
| Supported devices | Snapdragon 855–888+ | Snapdragon 8 Gen 1+ |
| Backend | CPU, GPU, DSP, NSP | CPU, GPU, HTP, sHTP |
| Model format | DLC | Model + Backend Libraries |
| Quantization | Per-layer INT8 | Per-channel INT8/INT16/FP16 |
| Dynamic shapes | Limited | Better support |
| Recommendation | Legacy/embedded | Use for new projects |
SNPE Conversion Pipeline
# Convert ONNX → SNPE DLC
snpe-onnx-to-dlc \
--input_network model.onnx \
--output_path model.dlc \
--input_dim input "1,3,224,224"
# Quantize with calibration data
snpe-dlc-quantize \
--input_dlc model.dlc \
--input_list calibration_list.txt \
--output_dlc model_quantized.dlc
# Profile on device (connected Android device)
snpe-net-run \
--container model_quantized.dlc \
--input_list input_list.txt \
--perf_profile high_performance \
--backend HTP \
--profiling_level detailed
QNN SDK Pipeline
# Convert ONNX → QNN
# From command line:
# qnn-onnx-converter --input_network model.onnx --output_path model_qnn
# Compile for device
# qnn-model-lib-generator --model model_qnn.cpp --lib_name model_qnn
# Run with QNN runtime
# qnn-net-run --model libmodel_qnn.so --backend libQnnHtp.so --input_list inputs.txt
Section 3: Qualcomm AI Hub — Cloud-Based Testing
Qualcomm AI Hub allows submitting models for profiling on real Snapdragon devices without owning one. It is the primary tool for the labs in this course.
import qai_hub
# Upload model
model = qai_hub.upload_model("model.onnx") # or torch model, tflite, etc.
# Compile for device
compile_job = qai_hub.submit_compile_job(
model=model,
device=qai_hub.Device("Snapdragon 8 Gen 3"),
input_specs={"input": ((1, 3, 224, 224), "float32")},
)
compiled_model = compile_job.get_target_model()
# Profile
profile_job = qai_hub.submit_profile_job(
model=compiled_model,
device=qai_hub.Device("Snapdragon 8 Gen 3"),
)
profile_result = profile_job.download_profile()
# Inspect per-layer timing
for layer in profile_result["layers"]:
print(f"{layer['name']:40s} {layer['execution_time_us']:8.1f} µs {layer['compute_unit']}")
# Inference
inference_job = qai_hub.submit_inference_job(
model=compiled_model,
device=qai_hub.Device("Snapdragon 8 Gen 3"),
inputs={"input": [np.random.randn(1, 3, 224, 224).astype(np.float32)]},
)
outputs = inference_job.download_output_data()
Reading the Profile Output
The profile output from AI Hub contains:
- layer name: maps back to ONNX node name
- execution_time_us: time in microseconds on the target device
- compute_unit: "HTP" (good), "CPU" (bad — means HTP fallback), "GPU"
- data_type: INT8 (fast), FP16 (slower on HTP)
Optimization workflow:
- Run initial profile → identify top-5 slowest layers
- Check compute_unit for each slow layer — if "CPU", that op is falling back
- For HTP fallbacks: check QNN op support matrix, then either (a) decompose the unsupported op, (b) replace with a supported alternative, or (c) quantize the op to INT8 (many FP32 ops unsupported on HTP but INT8 variants are)
- For slow HTP layers: check data type — FP16 is 4× slower than INT8 on HMX
Section 4: Op Mapping — PyTorch to HTP
| PyTorch Op | ONNX Op | HTP Support | Notes |
|---|---|---|---|
nn.Linear | Gemm | ✅ INT8/FP16 | Primary ML op |
nn.Conv2d | Conv | ✅ INT8/FP16 | |
nn.LayerNorm | LayerNormalization | ✅ FP16 | Slower than BN |
nn.BatchNorm2d | BatchNormalization | ✅ INT8 | Fuse into Conv |
torch.softmax | Softmax | ✅ FP16 | |
torch.sigmoid | Sigmoid | ✅ INT8/FP16 | |
torch.relu | Relu | ✅ INT8/FP16 | |
F.gelu | Gelu | ✅ FP16 | |
nn.MultiheadAttention | Attention (contrib) | ⚠️ Partial | Use QNN's custom attention kernel |
torch.einsum | N/A | ❌ | Decompose to matmul + transpose |
torch.unique | Unique | ❌ | CPU fallback — avoid in hot path |
tensor.nonzero() | NonZero | ❌ | Dynamic shape — CPU only |
| Custom Python ops | N/A | ❌ | Always CPU — eliminate these |
F.scaled_dot_product_attention | Attention | ✅ FP16 | Use directly |
Key insight: dynamic-shape ops (NonZero, Unique, scatter with dynamic indices) are almost always CPU-only. Design models to avoid them in the forward pass.
Section 5: Hardware-Aware Quantization for HTP
HTP achieves peak throughput with INT8 weights AND INT8 activations. Mixed-precision strategies:
Per-channel quantization (preferred for weights):
- Each output channel has its own scale/zero_point
- Better accuracy than per-tensor for weights
- Supported: INT8 per-channel for all linear/conv ops
Per-tensor quantization (for activations):
- Single scale/zero_point for the entire tensor
- Required for activations (per-channel activation quant not standard)
INT16 activations (Qualcomm extension):
- Some ops support INT16 activations with INT8 weights
- Better accuracy for sensitive layers (attention logits)
- 2× memory cost for activations vs INT8
Quantization-aware calibration on Qualcomm:
import qai_hub
import numpy as np
# Use 128–512 calibration samples (representative of deployment distribution)
calibration_data = {
"input": [sample.numpy() for sample in calibration_dataset[:128]]
}
compile_job = qai_hub.submit_compile_job(
model=model_onnx,
device=qai_hub.Device("Snapdragon 8 Gen 3"),
input_specs={"input": ((1, 3, 224, 224), "float32")},
options="--quantize_full_type int8 --quantize_io", # INT8 weights and activations
calibration_data=calibration_data,
)
Section 6: Power and Thermal Constraints
On mobile, thermal and power matter as much as performance:
- TDP: Snapdragon 8 Gen 3 sustained power ≈ 5W; burst ≈ 15W
- Thermal throttling: if SoC temperature > 85°C, clock speeds drop automatically
- Performance hints:
qai_hub.Deviceoptions includeperformance_profile(burst, sustained, low_power, power_saver) - Battery vs latency: at low_power mode, throughput ≈ 25% of burst, but battery drain ≈ 10% of burst
Engineering trade-off: at Qualcomm, the optimization target is often ops/joule (efficiency), not ops/second (throughput). A model that runs at 80% of peak TOPS but uses 40% of peak power may be preferred.
Section 7: Interview Pitfalls
| Question | Wrong Answer | Right Answer |
|---|---|---|
| "What is the HTP?" | "The GPU on Snapdragon" | "Hexagon Tensor Processor — a dedicated ML accelerator with HMX (matrix unit) + HVX (vector unit) + 8 MB VTCM on-chip SRAM; achieves ~73 TOPS INT8 on 8 Gen 3" |
| "Why does an op fall back to CPU on HTP?" | "It's not supported" | "Specific reasons: (1) op has dynamic shapes, (2) op is not in QNN's HTP op library for that data type, (3) data type mismatch (FP32 ops unsupported, use FP16 or INT8)" |
| "What is VTCM?" | "Video RAM" | "Vector Tightly Coupled Memory — 8 MB on-chip SRAM attached directly to HMX/HVX with ~2 TB/s bandwidth; weights processed from VTCM are 26× faster than from DRAM" |
| "SNPE vs QNN?" | "SNPE is newer" | "SNPE is the legacy SDK (2nd gen); QNN is the current SDK (3rd gen) with better quantization, wider device support, and more op coverage. Use QNN for new projects" |
| "How do you optimize a model for HTP throughput?" | "Quantize to INT8" | "Systematically: (1) profile to find slow/CPU-fallback layers, (2) eliminate dynamic-shape ops, (3) quantize to INT8 for HMX throughput, (4) check VTCM tiling for large weight tensors, (5) use QNN's fused attention op instead of decomposed attention" |
Section 8: Resources
- Qualcomm AI Hub documentation — https://aihub.qualcomm.com/docs
- QNN SDK — https://docs.qualcomm.com/bundle/publicresource/topics/80-63442-2/
- Qualcomm Innovation Center — AI Models — https://github.com/quic/ai-hub-models (100+ models with AI Hub integration)
- "Dissecting the Qualcomm Snapdragon NPU" — AnandTech deep-dive
- SNPE documentation — https://docs.qualcomm.com/bundle/publicresource/topics/80-63442-50/
- Hexagon DSP SDK — https://developer.qualcomm.com/software/hexagon-dsp-sdk
- qai-hub-models GitHub — https://github.com/quic/ai-hub-models — production examples of PyTorch → HTP deployments