# SIMD 是什么

> SSE/AVX/NEON 一条指令算多个数——CPU 上的向量化与 NumPy 的底层。

- 作者：David（道雾轩）
- 专栏：从 Bit 到 AGI（https://daiw.org/manual/bit-to-agi.md）
- 最后更新：2026-08-03
- 原文：https://daiw.org/manual/bit-to-agi/01-bits/what-is-simd
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

GPU 用成千上万线程并行；CPU 也有**单指令多数据**（SIMD）：一条指令同时对 **4/8/16/32 个** float 做加法。NumPy 快、PyTorch CPU 后端部分算子快，背后常常是 **AVX-512** 或 **ARM NEON**，而不是 Python 魔法。

## 直觉：一次端四杯 vs 四趟各端一杯

标量：`c[i] = a[i] + b[i]` 循环 1024 次，每次 1 个 float。SIMD：一次 `__m256` 寄存器装 **8 个** float，一条 `vaddps` 加 8 对——理论 **8×** 吞吐（若内存跟得上）。

## 定义

| ISA | 平台 | 典型宽度 | 寄存器示例 |
| --- | ---- | -------- | ---------- |
| SSE | x86 | 128 bit = 4×float32 | XMM |
| AVX2 | x86 | 256 bit = 8×float32 | YMM |
| AVX-512 | x86 | 512 bit = 16×float32 | ZMM |
| NEON | ARM | 128 bit | V 寄存器 |

**向量化**（auto-vectorization）：编译器把循环改 SIMD；**intrinsics**：程序员手写 ` _mm256_add_ps`。

PyTorch `torch.add` 在 CPU 上 dispatch 到 **vectorized** kernel（ATen）。

## 例子一：理论加速比

float32 向量加，长度 $N=10^6$：
- 标量：$N$ 次 add + 循环开销
- AVX2：$N/8$ 次向量 add

峰值 8×；实际 **4–6×** 常见（load/store、尾部处理、带宽）。

## 例子二： reduction 为何难向量化

```python
# 依赖上一轮结果 — 串行依赖
s = 0.0
for x in data:
    s += x
```

求和有 **loop-carried dependency**；编译器用 **partial sum + horizontal add** 或 Kahan。BLAS `sdot` 高度优化即此类。

对比：无依赖逐元素：

```python
# c[i] = a[i] * b[i] + 1  — 完美 SIMD
```

## 与 GPU SIMT 对比

| | SIMD（CPU） | SIMT（GPU） |
| -- | ----------- | ----------- |
| 线程模型 | 少量宽向量 lane | 大量轻量线程 |
| 分支 | 全 lane 执行相同 op | warp diverge |
| 规模 | 8–16 宽 | 32×数千 |

CPU 上小矩阵仍用 SIMD；大矩阵 offload GPU。

## AI 中的 SIMD

- **ONNX Runtime / oneDNN**：CPU inference 用 AVX512 VNNI 做 INT8 dot product。
- **Apple M 系列 AMX**：矩阵扩展，接近小型 NPU。
- **NumPy**：`np.add(a,b)` 底层 ufunc 循环 SIMD。

Whisper small CPU 实时因子 ~0.5–2×（视核数）；无 SIMD 优化可能 **10×+** 慢。

## 编译器自动向量化条件

循环需：**计数已知**、**无函数调用**、**无指针别名**（`restrict`）、**步长 1**。`#pragma omp simd` 或 GCC `__attribute__((optimize("tree-vectorize")))`) 提示编译器。

PyTorch ATen 里 `Vectorized<T>` 抽象 SSE/AVX/NEON；`float` 向量加 dispatch 到 `binary_kernel` 的 SIMD 路径。你写 `a + b` 不必写 intrinsics，但 **Python for 循环** 到不了这层。

## AMX 与 NPU 方向

Intel **AMX**（Sapphire Rapids+）：tile 寄存器做 **16×16 int8** 矩阵块累加，服务 INT8 inference。**Apple Neural Engine**、高通 **Hexagon** 类似——**专用矩阵单元** 是 SIMD 的进化形态，功耗更低。

**RISC-V Vector**（RVV）：可变长向量，长度运行时定，未来 edge AI 可能普及。

## Eigen / MKL 与 BLAS 层级

`numpy.dot`、`torch.mm` 底层 **OpenBLAS / MKL / cuBLAS**。MKL 在 Intel CPU 上常快 **2–5×** OpenBLAS；AMD CPU 可用 AOCL。你写 Python，实际 hot path 是 **Fortran 77 风格 GEMM** 调 SIMD + 多线程——**几十年 BLAS 生态** 供养 AI CPU fallback。

**Batch size 1 GEMV vs GEMM**：矩阵-向量乘 GPU 利用率低；batched GEMM 才是 peak——**推理框架 batching 的服务器端理由**。

## 场景实战：numpy 与手写 Python 循环

```python
import numpy as np, time
n = 5_000_000
a = np.random.rand(n).astype(np.float32)
b = np.random.rand(n).astype(np.float32)

t0 = time.perf_counter(); c = a + b; print("numpy ms", (time.perf_counter()-t0)*1000)

# 勿对 500 万元素真跑纯 Python list
```

500 万元素 float32 加法，NumPy **~几 ms**（AVX 宽向量 + 可能多线程）；纯 Python for **~秒级**。差距 **100×+** 来自 **SIMD + C 循环**，不是 Python 语法 magic。PyTorch CPU tensor 同理 dispatch ATen vectorized kernel。

## 常见误区

1. **「开 -O3 一定向量化」**——指针别名、复杂索引、函数调用阻碍；看 `-Rpass=loop-vectorize` 或 `numpy.show_config()`。
2. **「AVX-512 永远更快」**——部分 CPU 降频（frequency scaling）；短向量 workload 不一定赚。
3. **「Python 多线程能 SIMD 并行」**——GIL 限制 CPU bound Python；应用 SIMD 在 C 扩展里单线程宽向量，或多进程。

## 动手试一试

查看 NumPy/BLAS 链接与 CPU 特性：

```python
import numpy as np
np.show_config()  # 看 OpenBLAS/MKL、AVX 等

a = np.random.rand(1_000_000).astype(np.float32)
b = np.random.rand(1_000_000).astype(np.float32)
# 对比 float32 vs Python list 循环（勿对大 list 真跑）
c = a + b  # SIMD 路径
```

Linux 上：`lscpu | grep -i avx`

## 延伸思考

**Apple Accelerate**、**Intel MKL-DNN**、**ARM Compute Library** 各把 SIMD 藏在 BLAS/Conv API 后；**PyTorch Inductor** 生成 **Triton** 内核时自动选 **tile size** 匹配 **SIMD 宽 × register 数**。手写 kernel 的工程师读 **SASS/PTX** 见 `fma.rn` 一次算两个 float——**SIMD 是显式的**。Edge **INT8 VNNI** 一条指令 **4× int8 乘加**，MobileNet 在 CPU 上实时，靠 **bit 窄 + SIMD 宽** 而非 GPU。

## 温故：CPU 仍是 AI 栈的一翼

Tokenizer、HTTP、调度、数据库、feature store 跑 CPU；**SIMD 让 CPU 预处理不拖 GPU 后腿**。边缘设备无 GPU 时 **INT8 + AVX/VNNI** 是唯一出路。别只盯 CUDA——**全 pipeline 的 bit 吞吐** 才决定用户 latency。

## 补充：向量长度与 AVX-512

Ice Lake / Sapphire Rapids 服务器 CPU 有 **AVX-512**；消费级 Intel 12–14 代部分型号 **禁用 AVX-512** 以省 die。**PyTorch wheel** 发布时带 **多种 CPU dispatch**——同一 `torch.add` 在运行时选 AVX2 或 AVX512 路径。装 **MKL** 版 PyTorch 前，查 `lscpu` 确认 flags，避免 **买了 AVX-512 机器却跑 AVX2 路径** 浪费 **~30% 向量吞吐**。

## 补充：OpenMP 与 SIMD

`OMP_NUM_THREADS=1` 时 NumPy 仍可能 **多线程 BLAS**（MKL 默认）。`MKL_NUM_THREADS=1` + 单线程 Python 才能 isolate **SIMD 增益** benchmark。** oversubscribe** 线程（8 核开 32 BLAS 线程）常 **变慢**——与 GPU occupancy 调优 analogous。


## 读完带走

CPU 向量宽 **256–512 bit**；一次 **8–16 float** 并行。NumPy/PyTorch CPU 路径 **默认吃满 SIMD**；Python for 不吃。预处理 pipeline 是 **免费优化区**。

## 小结

SIMD = 一条指令处理一个向量寄存器里的多个标量。CPU 靠它提升吞吐；与 GPU 大规模并行互补。理解 SIMD，就理解为何「写 Python 循环慢、调 NumPy 快」。**DataLoader 预处理** 尽量用 NumPy/Polars 向量算子，把 Python 循环留给业务逻辑，是免费 perf win。**Tokenization** 若纯 Python BPE，常是 CPU 瓶颈之一——Rust/HF tokenizers 用 SIMD + 并行。**prefetch** 下一 batch 与 GPU 重叠可 hide 延迟。

动手作业：打开 `nvidia-smi` 或活动监视器，对照本章公式估算当前进程的 **权重与 KV 占用**，误差在一个数量级内即合格。

**AVX-512** 服务器 CPU 向量化宽度翻倍；笔记本可能无此 ISA。

**Polars/DuckDB** 列式计算同样靠 SIMD；ETL 与训练前清洗可受益。

**OpenMP parallel for** 与 **SIMD** 是正交两维；两者同时开才吃满 CPU。

写 **C++ 扩展** 时 `#pragma omp simd` 与 `-march=native` 可让自定义算子接近 NumPy 速度；纯 Python 循环无此捷径。

**RISC-V Vector** 可变长向量是 edge AI 潜在标准；与固定宽 AVX 哲学不同。

**`numpy.show_runtime()`** 可看 BLAS 线程与 SIMD 库链接情况（若版本支持）。

**Apple Accelerate vDSP** 封装 SIMD；Swift/Objective-C ML 预处理常走此路径而非裸 intrinsics。

**Wasm SIMD128** 在浏览器跑小模型时可用；大 LLM 仍靠服务器 GPU。

**Eigen::Map** 把已有 buffer 当矩阵，零拷贝对接 C++ 推理引擎时常用。

**向量内积 dot** 在 BLAS 里高度优化；Attention 点积本质是 **batched dot**。

下一章 **向量点积** 会形式化 Attention 里的 Q·K 运算。

**Horner 法** 多项式求值可向量化；标量循环则慢。

## 第一章衔接

本章用 **bit 语言** 描述 AI 底座；第二章 **[数学](https://daiw.org/manual/bit-to-agi/02-math)** 用 **向量与矩阵** 重述同一物理过程。两章对照读：**bytes 是存储，FLOPs 是计算，带宽是瓶颈**——三个量构成工程三角。SIMD 与 GPU 并行互补，构成现代 AI 算力底座。

下一篇：[矩阵为什么连续存储](https://daiw.org/manual/bit-to-agi/01-bits/why-matrices-are-contiguous)——行优先与 stride 如何服务 SIMD/GPU。
