端侧推理 端侧推理(edge inference)把模型跑在用户设备上(手机、笔记本、IoT 传感器),不把数据发到云端。本文件涵盖端侧约束、模型压缩流水线、端侧运行时、编译器栈、硬件目标(NPU、神经引擎)、端侧 LLM、联邦学习,以及延迟优化 云端推理需要联网、增加延迟(往返 50-200 ms)、每次请求都要花钱,还会把用户数据发给第三方服务器。端侧推理一举消除这四样:模型在本地跑、即时响应、每次推理零成本、数据隐私得以保留。 权衡:端侧设备的算力和内存比数据中心 GPU 少 100-1000 倍。要让模型在这些约束下跑起来,需要在每一层做激进的优化。 Cactus(开源项目,仓库位于 github.
端侧推理(edge inference)把模型跑在用户设备上(手机、笔记本、IoT 传感器),不把数据发到云端。本文件涵盖端侧约束、模型压缩流水线、端侧运行时、编译器栈、硬件目标(NPU、神经引擎)、端侧 LLM、联邦学习,以及延迟优化
云端推理需要联网、增加延迟(往返 50-200 ms)、每次请求都要花钱,还会把用户数据发给第三方服务器。端侧推理一举消除这四样:模型在本地跑、即时响应、每次推理零成本、数据隐私得以保留。
权衡:端侧设备的算力和内存比数据中心 GPU 少 100-1000 倍。要让模型在这些约束下跑起来,需要在每一层做激进的优化。
Cactus(开源项目,仓库位于 github.com/cactus-compute/cactus)是一个为移动和可穿戴设备量身打造的低延迟 AI 引擎。它在生产中演示了本文件讲到的很多技术:用于注意力和矩阵运算的自定义 ARM SIMD kernel(第 16 章)、KV-cache 量化(第 17 章文件 01)、分块预填充、在 Apple 和高通芯片上的 NPU 加速推理、用零拷贝内存映射把内存占用降到十分之一,以及在端侧算力不足时自动回退到云端。Cactus 支持 iOS、Android、macOS 和嵌入式 Linux 上的多模态推理(LLM、视觉、语音),并提供 Swift、Kotlin、Python、Flutter、React Native 和 Rust 的 SDK。它的基准测试显示:在 M4 Pro 上对 1.2B 模型做 INT4 推理,解码速度可达 100 tokens/s;在 iPhone 17 Pro 上可达 48 tokens/s——这是优化过的端侧推理具体长什么样。
| 资源 | 云端 GPU(H100) | 笔记本(M4) | 手机(Snapdragon 8 Gen 3) | IoT(ESP32) |
|---|---|---|---|---|
| 内存 | 80 GB HBM3 | 16-36 GB 统一内存 | 8-12 GB LPDDR5 | 520 KB |
| 算力 | 989 TFLOPS(FP8) | 38 TOPS(神经引擎) | 45 TOPS(NPU) | 0.001 TOPS |
| 功耗 | 700 W | 15-30 W | 5-10 W | 0.1 W |
| 存储 | TB 级 | 256 GB-2 TB | 128-512 GB | 4 MB |
完整模型(FP32,70B 参数) ↓ 知识蒸馏 → 更小的模型(7B 参数) ↓ 结构化剪枝 → 移除冗余头/层(有效 4B) ↓ 量化(INT4)→ 缩小 4 倍(2 GB) ↓ 编译器优化 → 融合 kernel、优化内存布局 ↓ 运行时 → 端侧执行
**运行时(runtime)**加载模型、分配内存,并在目标硬件上执行推理。每个平台都有自己的首选运行时:
ONNX Runtime:跨平台(Windows、Linux、macOS、iOS、Android)。支持 CPU、GPU(CUDA、DirectML、CoreML、NNAPI)以及众多加速器后端。可移植性最好。模型从 PyTorch/TensorFlow 导出为 ONNX 格式。
TensorFlow Lite(TFLite):Google 的端侧运行时。针对 ARM CPU 和 Android NPU 优化。二进制很小(约 1 MB)。支持 INT8 和 float16。是 Android 部署的标准。
Core ML:Apple 在 iOS/macOS 上的运行时。根据模型特征自动使用神经引擎、GPU 或 CPU。模型用 coremltools 从 PyTorch/TensorFlow 转换。与 Apple 硬件深度集成(统一内存、神经引擎)。
ExecuTorch:Meta 新的端侧 PyTorch 运行时。专为端侧部署设计,支持提前编译(AOT)和算子级的硬件加速器委派。是 PyTorch Mobile 的继任者。
TensorRT:NVIDIA 的 GPU 推理优化运行时(第 15 章)。融合层、选择最优 kernel、自动量化。在 NVIDIA GPU 上比 PyTorch eager 模式快 2-5 倍。
llama.cpp:单文件 C++ 的 LLM 推理引擎。支持 GGUF 量化(Q4、Q5、Q8)、CPU(AVX/NEON)、Metal(Apple GPU)、CUDA 和 Vulkan。在消费级硬件上跑 LLM 的首选。
PyTorch 模型 ↓ 导出(torch.export、ONNX、TorchScript) 图 IR(中间表示) ↓ 图优化 - 常量折叠(编译期算出常量表达式) - 死代码消除(移除无用算子) - 算子融合(conv + bn + relu → 单个融合算子) - 布局变换(NCHW → NHWC,适配 ARM、channels-last) ↓ lowering 硬件特定 IR ↓ 后端优化 - 分块和循环序(cache 友好的访问模式) - 向量化(SIMD,第 16 章) - 内存规划(复用缓冲区以降低峰值内存) - kernel 选择(为每个算子选最优实现) ↓ 代码生成 机器码 / NPU 指令
**算子融合(operator fusion)**是影响最大的优化。一个 transformer 块大约有 20 个算子(矩阵乘、加、layernorm、softmax 等)。不融合的话,每个算子都要把输出写回显存,下一个再读回来。融合后,多个算子合并成一个 kernel,数据一直留在寄存器/缓存里。这能快 2-5 倍(第 16 章 roofline 模型)。
内存规划(memory planning):编译器分析模型图,判断哪些张量生命周期重叠、可以共用同一块内存缓冲。一个有 100 个中间张量的模型,可能只需要 10 个张量的内存,因为大多数张量在别的张量诞生前就被消费、释放了。这对内存有限的设备至关重要。
Qualcomm Adreno(Android):支持 OpenCL、Vulkan compute(第 16 章)以及高通自家的 SNPE(Snapdragon Neural Processing Engine)。Adreno GPU 有 256-1024 个 ALU,支持 FP16 和 INT8。
ARM Mali(Android):支持 OpenCL 和 Vulkan。Mali GPU 采用基于分块的架构(与桌面 GPU 不同),这会影响最优的内存访问模式。
Apple GPU(iOS/macOS):通过 Metal(Apple 的 GPU API)访问。统一内存架构意味着没有 CPU↔GPU 拷贝开销。Metal Performance Shaders(MPS)提供优化过的 ML 原语。
NPU 是专门为 ML 推理设计的固定功能加速器。对于标准 ML 运算(矩阵乘、卷积、激活),它比 GPU 能效高得多。
Apple 神经引擎(Neural Engine):16 核,约 38 TOPS(INT8)。通过 Core ML 访问。非常适合视觉模型和端侧扩散模型。不能跑任意代码——只支持 Core ML 支持的算子。
Qualcomm Hexagon NPU:集成在 Snapdragon SoC 里。支持 INT8 和 INT4 推理。通过 SNPE 或带 QNN 后端的 ONNX Runtime 访问。支撑背景虚化、语音识别、实时翻译等端侧功能。
Google Edge TPU:云端 TPU 的小型低功耗版本。4 TOPS,2W。用于 Coral 设备上的端侧推理。只支持 INT8 量化的 TFLite 模型。
委派模式(delegation pattern):运行时把模型图在 NPU(跑支持的算子)和 CPU(跑不支持的算子)之间切分。让尽量多的部分跑在 NPU 上,是性能和能效的关键。
| 模型 | 参数 | 量化后大小 | 目标设备 | 性能 |
|---|---|---|---|---|
| Phi-3 Mini | 3.8B | ~2 GB(Q4) | 手机/笔记本 | iPhone 15 上约 15 tokens/s |
| Gemma 2B | 2B | ~1.5 GB(Q4) | 手机 | Pixel 8 上约 20 tokens/s |
| Llama 3.2 1B | 1B | ~700 MB(Q4) | 手机 | 约 30 tokens/s |
| Llama 3.2 3B | 3B | ~2 GB(Q4) | 手机/笔记本 | 约 15 tokens/s |
| Llama 3.1 8B | 8B | ~4.5 GB(Q4) | 笔记本 | M2 上约 20 tokens/s |
挑战:
llama.cpp 是端侧 LLM 的事实标准。它能在 CPU(AVX2、NEON、I8MM)、Apple GPU(Metal)、NVIDIA GPU(CUDA)、AMD GPU(ROCm/Vulkan),甚至手机(通过 Android 上的 Termux)上跑。
**联邦学习(federated learning)**在不集中数据的前提下,跨众多设备训练模型。每个设备在自己的本地数据上训练,算出梯度更新,只把更新(不是数据)发给一个聚合更新的中心服务器。
算法(FedAvg):
隐私:原始数据从不离开设备。服务器只看到聚合后的模型更新。**差分隐私(differential privacy)**给更新加噪声,使单个数据点无法从梯度反推出来。
通信效率:模型更新很大(和模型一样大)。压缩技术能减小它:梯度量化(发 INT8 梯度而非 FP32)、稀疏化(sparsification)(只发最大的那些梯度)、梯度累积(多做本地步数、少发几次)。
应用:Google 键盘预测(Gboard)、Apple 语音识别、健康监测(在敏感健康数据上训练但不集中存储)。
除了压缩,还有几种技术能降低端到端推理延迟:
提前退出(early exit):在中间层加分类头。如果模型在第 6 层(共 24 层)就 confident,就直接返回预测,不跑 7-24 层。简单输入提前退出,困难输入用完整模型。对难易混合的任务,平均延迟显著下降。
模型切分(model partitioning):把模型在 NPU(矩阵乘高效)、GPU(不规则运算高效)和 CPU(其余都兜底)之间切分。编译器根据 profiling 决定每个算子去哪跑。
缓存(caching):对有重复查询的应用(自动补全、代码补全),缓存最近的计算。如果用户输入"如何",而模型最近为"如何"生成过补全,缓存的 KV-cache 就能复用,完全跳过预填充阶段。
投机预取(speculative prefetching):预测用户接下来会做什么,在用户开口前就启动推理。一个聊天应用可以在用户读当前回答时,就开始为可能的后续问题生成回复。
def compression_pipeline(original_params_M, original_bits=32): size_mb = original_params_M * 1e6 * original_bits / 8 / 1e6 print(f"Original: {original_params_M}M params, {original_bits}-bit → {size_mb:.0f} MB") # 第 1 步:知识蒸馏(减少参数) distilled_params = original_params_M * 0.15 # 70B → 约 10B 等效 size_mb = distilled_params * 1e6 * original_bits / 8 / 1e6 print(f"After distillation ({distilled_params:.0f}M params): {size_mb:.0f} MB") # 第 2 步:结构化剪枝(再移除 30%) pruned_params = distilled_params * 0.7 size_mb = pruned_params * 1e6 * original_bits / 8 / 1e6 print(f"After pruning ({pruned_params:.0f}M params): {size_mb:.0f} MB") # 第 3 步:INT4 量化 size_mb = pruned_params * 1e6 * 4 / 8 / 1e6 print(f"After INT4 quantisation: {size_mb:.0f} MB") print(f"Total compression: {original_params_M * 1e6 * original_bits / 8 / 1e6 / size_mb:.0f}x") print("=== Starting from 70B model ===") compression_pipeline(70000) print("\n=== Starting from 7B model ===") compression_pipeline(7000)
def estimate_latency(model_name, params_M, bits, compute_tops, mem_bw_gbs, seq_len=256): """估算访存带宽受限模型的 token 生成延迟。""" # 模型大小(字节) model_bytes = params_M * 1e6 * bits / 8 # 解码是访存受限的:每个 token 都要搬整个模型 time_per_token_ms = model_bytes / (mem_bw_gbs * 1e9) * 1000 # 每秒 token 数 tokens_per_sec = 1000 / time_per_token_ms print(f"{model_name}: {params_M/1000:.1f}B params @ {bits}-bit = {model_bytes/1e9:.1f} GB") print(f" Memory bandwidth: {mem_bw_gbs} GB/s") print(f" Time per token: {time_per_token_ms:.1f} ms") print(f" Tokens/sec: {tokens_per_sec:.0f}") print() # Apple M2 Pro:200 GB/s 统一内存带宽 print("=== Apple M2 Pro (200 GB/s) ===") estimate_latency("Llama-7B Q4", 7000, 4, 15.8, 200) estimate_latency("Llama-7B Q8", 7000, 8, 15.8, 200) estimate_latency("Llama-70B Q4", 70000, 4, 15.8, 200) # 手机(Snapdragon 8 Gen 3):约 50 GB/s LPDDR5 print("=== Snapdragon 8 Gen 3 (50 GB/s) ===") estimate_latency("Phi-3 Mini Q4", 3800, 4, 45, 50) estimate_latency("Llama-3B Q4", 3000, 4, 45, 50)