2536c937e3
翻译自英文原版 maths-cs-ai-compendium,共 20 章全部完成。 第01章 向量 | 第02章 矩阵 | 第03章 微积分 第04章 统计学 | 第05章 概率论 | 第06章 机器学习 第07章 计算语言学 | 第08章 计算机视觉 | 第09章 音频与语音 第10章 多模态学习 | 第11章 自主系统 | 第12章 图神经网络 第13章 计算与操作系统 | 第14章 数据结构与算法 第15章 生产级软件工程 | 第16章 SIMD与GPU编程 第17章 AI推理 | 第18章 ML系统设计 第19章 应用人工智能 | 第20章 前沿人工智能 翻译说明: - 所有数学公式 $...$ / $$...$$、代码块、图片引用完整保留 - mkdocs.yml 配置中文导航 + language: zh - README.md 已翻译为中文(兼 docs/index.md) - docs/ 目录包含指向各章文件的 symlink - 约 29,000 行中文内容,排除 .cache/ 构建缓存
213 lines
13 KiB
Markdown
213 lines
13 KiB
Markdown
# 边缘推理
|
||
|
||
*边缘推理在用户设备(手机、笔记本电脑、物联网传感器)上运行模型,无需将数据发送到云端。本文涵盖边缘限制、模型压缩流水线、设备端运行时、编译器栈、硬件目标(NPU、神经引擎)、设备端LLM、联邦学习和延迟优化*
|
||
|
||
- 云端推理需要网络连接,增加延迟(50-200毫秒往返),每次请求花费金钱,并将用户数据发送到第三方服务器。**边缘推理**消除了所有四个问题:模型本地运行,即时响应,每次推理零成本,且数据保持私密。
|
||
|
||
- 权衡:边缘设备的计算和内存比数据中心GPU小100-1000倍。使模型在这些约束下运行需要在每个层面进行积极优化。
|
||
|
||
- **Cactus**([github.com/cactus-compute/cactus](https://github.com/cactus-compute/cactus)) 是一个专为移动和可穿戴设备构建的低延迟AI引擎。它在生产中展示了本文涵盖的许多技术:自定义ARM SIMD内核用于注意力和矩阵运算(第16章)、KV缓存量化(第17章文件01)、分块预填充、Apple和Qualcomm芯片上的NPU加速推理、零拷贝内存映射实现10倍更低的RAM使用,以及在设备端计算不足时的自动云回退。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 |
|
||
|
||
- 云GPU和手机NPU之间的计算差距约为20倍。GPU和微控制器之间的差距约为1,000,000倍。不同设备需要不同程度的压缩和不同的模型架构。
|
||
|
||
## 模型压缩流水线
|
||
|
||
- 对于边缘部署,压缩不是单一技术——它是一个按顺序应用的互补技术**流水线**:
|
||
|
||
```
|
||
完整模型(FP32,70B参数)
|
||
↓ 知识蒸馏 → 更小模型(7B参数)
|
||
↓ 结构化剪枝 → 移除冗余头/层(4B有效)
|
||
↓ 量化(INT4) → 4倍更小(2 GB)
|
||
↓ 编译器优化 → 融合内核,优化内存布局
|
||
↓ 运行时 → 设备端执行
|
||
```
|
||
|
||
- 每一步减少大小和延迟。顺序很重要:先蒸馏(减少架构),然后剪枝(移除结构),然后量化(降低精度),最后编译(为目标硬件优化)。在量化之后进行蒸馏会试图压缩已经损失质量的模型。
|
||
|
||
## 设备端运行时
|
||
|
||
- **运行时**加载模型、分配内存并在目标硬件上执行推理。每个平台有其偏好的运行时:
|
||
|
||
- **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运行时。专为边缘部署设计,具有提前编译和操作级硬件加速器委派功能。PyTorch Mobile的继任者。
|
||
|
||
- **TensorRT**:NVIDIA的GPU推理优化运行时(第15章)。融合层、选择最优内核并自动量化。在NVIDIA GPU上比PyTorch eager模式快2-5倍。
|
||
|
||
- **llama.cpp**:用于LLM的单文件C++推理引擎。支持GGUF量化(Q4、Q5、Q8)、CPU(AVX/NEON)、Metal(Apple GPU)、CUDA和Vulkan。在消费级硬件上运行LLM的首选方案。
|
||
|
||
## 编译器栈
|
||
|
||
- 在高级模型(PyTorch图)和硬件(NPU指令)之间是**编译器栈**,它为特定目标优化模型:
|
||
|
||
```
|
||
PyTorch模型
|
||
↓ 导出(torch.export、ONNX、TorchScript)
|
||
图IR(中间表示)
|
||
↓ 图优化
|
||
- 常量折叠(编译时计算常量表达式)
|
||
- 死代码消除(移除未使用的操作)
|
||
- 算子融合(conv + bn + relu → 单个融合操作)
|
||
- 布局转换(NCHW → NHWC用于ARM,通道最后)
|
||
↓ 降级
|
||
硬件特定IR
|
||
↓ 后端优化
|
||
- 分块和循环排序(缓存友好的访问模式)
|
||
- 向量化(SIMD,第16章)
|
||
- 内存规划(重用缓冲区以最小化峰值内存)
|
||
- 内核选择(为每个操作选择最佳实现)
|
||
↓ 代码生成
|
||
机器代码 / NPU指令
|
||
```
|
||
|
||
- **算子融合**是影响最大的优化。一个Transformer块约有20个操作(矩阵乘、加法、层归一化、softmax等)。没有融合,每个操作将其输出写入内存,下一个操作再读回。有了融合,多个操作组合成一个内核,将数据保留在寄存器/缓存中。这可以使速度快2-5倍(第16章,屋顶模型)。
|
||
|
||
- **内存规划**:编译器分析模型图以确定哪些张量的生命周期重叠,可以共享相同的内存缓冲区。一个有100个中间张量的模型可能只需要10张量的内存,因为大多数张量在其他张量创建之前就被消耗和释放了。这在内存有限的设备上至关重要。
|
||
|
||
## 硬件目标
|
||
|
||
### 移动GPU
|
||
|
||
- **Qualcomm Adreno**(Android):支持OpenCL、Vulkan计算(第16章)和Qualcomm专有的SNPE(Snapdragon神经处理引擎)。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)
|
||
|
||
- NPU是专门为ML推理设计的固定功能加速器。它们在标准ML操作(矩阵乘、卷积、激活)上比GPU节能得多。
|
||
|
||
- **Apple神经引擎**:16核,约38 TOPS(INT8)。通过Core ML访问。非常适合视觉模型和设备端扩散。不能运行任意代码——只支持Core ML支持的操作。
|
||
|
||
- **Qualcomm Hexagon NPU**:集成到Snapdragon SoC中。支持INT8和INT4推理。通过SNPE或ONNX Runtime(带QNN后端)访问。为设备端功能如背景虚化、语音识别和实时翻译提供支持。
|
||
|
||
- **Google Edge TPU**:云端TPU的小型低功耗版本。4 TOPS,2W。用于Coral设备进行设备端推理。仅支持INT8量化的TFLite模型。
|
||
|
||
- **委派模式**:运行时在NPU(用于支持的操作)和CPU(用于不支持的操作)之间拆分模型图。最大化在NPU上运行的部分是性能和能效的关键。
|
||
|
||
## 设备端LLM
|
||
|
||
- 在手机和笔记本电脑上运行LLM已变得可行,得益于小模型和积极的量化:
|
||
|
||
| 模型 | 参数 | 量化后大小 | 目标设备 | 性能 |
|
||
|-------|--------|---------------|---------------|-------------|
|
||
| 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 |
|
||
|
||
- **挑战**:
|
||
- **内存**:3B Q4模型占2 GB,但长对话的KV缓存增加了显著额外内存。手机上的上下文长度通常限制在2-4K token。
|
||
- **热节流**:持续推理使手机发热。连续生成30秒后,SoC会降低时钟速度以防止过热,性能下降30-50%。
|
||
- **电池**:以15 tokens/s运行3B模型消耗约3-5W。30分钟的对话消耗典型手机电池约5%。偶尔使用可以接受,但始终在线应用存在问题。
|
||
|
||
- **llama.cpp**是设备端LLM的标准。它在CPU(AVX2、NEON、I8MM)、Apple GPU(Metal)、NVIDIA GPU(CUDA)、AMD GPU(ROCm/Vulkan)甚至手机上(通过Android上的Termux)运行。
|
||
|
||
## 联邦学习
|
||
|
||
- **联邦学习**在许多设备上训练模型,无需集中数据。每个设备在其本地数据上训练,计算梯度更新,并将只有更新(而非数据)发送到聚合更新的中央服务器。
|
||
|
||
- **算法**(FedAvg):
|
||
1. 服务器将当前模型发送给$K$个选定设备。
|
||
2. 每个设备在其本地数据上微调模型几步。
|
||
3. 每个设备将其更新后的模型(或差异)发送回服务器。
|
||
4. 服务器平均更新:$W_{\text{new}} = \frac{1}{K} \sum_{k=1}^{K} W_k$。
|
||
5. 重复。
|
||
|
||
- **隐私**:原始数据从不离开设备。服务器只看到聚合的模型更新。**差分隐私**向更新添加噪声,使得无法从梯度中逆向推断单个数据点。
|
||
|
||
- **通信效率**:模型更新很大(与模型相同大小)。压缩技术减少了这一点:**梯度量化**(发送INT8梯度而不是FP32)、**稀疏化**(只发送最大的梯度)和**梯度累积**(做更多本地步骤,发送更少频率)。
|
||
|
||
- **应用**:Google的键盘预测(Gboard)、Apple的语音识别、健康监测(在敏感健康数据上训练而不集中数据)。
|
||
|
||
## 延迟优化
|
||
|
||
- 除了压缩,还有几种技术减少端到端推理延迟:
|
||
|
||
- **提前退出**:在中间层添加分类头。如果模型在第6层(共24层)已经自信,则返回预测而不运行第7-24层。简单输入提前退出,困难输入使用完整模型。对于混合简单和困难输入的任务,平均延迟显著下降。
|
||
|
||
- **模型分区**:在NPU(对矩阵乘高效)、GPU(对不规则操作高效)和CPU(处理其他一切)之间拆分模型。编译器根据性能分析决定哪些操作去哪里。
|
||
|
||
- **缓存**:对于具有重复查询的应用(自动补全、代码补全),缓存最近的计算。如果用户输入"How do I"且模型最近生成了"How do I"的补全,可以重用缓存的KV缓存,完全跳过预填充阶段。
|
||
|
||
- **推测性预取**:预测用户下一步将做什么,在用户询问之前开始推理。聊天应用可能在用户阅读当前答案时开始生成可能后续问题的响应。
|
||
|
||
## 编程任务(使用CoLab或notebook)
|
||
|
||
1. 模拟模型压缩流水线。从float32模型开始,依次应用蒸馏(模拟)、剪枝和量化,并跟踪每一步的大小。
|
||
```python
|
||
def compression_pipeline(original_params_M, original_bits=32):
|
||
size_mb = original_params_M * 1e6 * original_bits / 8 / 1e6
|
||
|
||
print(f"原始: {original_params_M}M 参数, {original_bits}-位 → {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"蒸馏后 ({distilled_params:.0f}M 参数): {size_mb:.0f} MB")
|
||
|
||
# 步骤2:结构化剪枝(移除剩余30%)
|
||
pruned_params = distilled_params * 0.7
|
||
size_mb = pruned_params * 1e6 * original_bits / 8 / 1e6
|
||
print(f"剪枝后 ({pruned_params:.0f}M 参数): {size_mb:.0f} MB")
|
||
|
||
# 步骤3:INT4量化
|
||
size_mb = pruned_params * 1e6 * 4 / 8 / 1e6
|
||
print(f"INT4量化后: {size_mb:.0f} MB")
|
||
|
||
print(f"总压缩比: {original_params_M * 1e6 * original_bits / 8 / 1e6 / size_mb:.0f}x")
|
||
|
||
print("=== 从70B模型开始 ===")
|
||
compression_pipeline(70000)
|
||
|
||
print("\n=== 从7B模型开始 ===")
|
||
compression_pipeline(7000)
|
||
```
|
||
|
||
2. 估计设备端推理延迟。给定模型的操作计数和硬件规格,计算是否满足延迟目标。
|
||
```python
|
||
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 参数 @ {bits}-位 = {model_bytes/1e9:.1f} GB")
|
||
print(f" 内存带宽: {mem_bw_gbs} GB/s")
|
||
print(f" 每token时间: {time_per_token_ms:.1f} ms")
|
||
print(f" Tokens/秒: {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)
|
||
```
|