从 Bit 到 AGI第一章:比特(Bit)

SIMD 是什么

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

作者 David更新于 第 14 篇(共 186 篇)

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平台典型宽度寄存器示例
SSEx86128 bit = 4×float32XMM
AVX2x86256 bit = 8×float32YMM
AVX-512x86512 bit = 16×float32ZMM
NEONARM128 bitV 寄存器

向量化(auto-vectorization):编译器把循环改 SIMD;intrinsics:程序员手写 _mm256_add_ps。

PyTorch torch.add 在 CPU 上 dispatch 到 vectorized kernel(ATen)。

例子一:理论加速比

float32 向量加,长度 N=106N=10^6:

  • 标量:NN 次 add + 循环开销
  • AVX2:N/8N/8 次向量 add

峰值 8×;实际 4–6× 常见(load/store、尾部处理、带宽)。

例子二: reduction 为何难向量化

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

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

对比:无依赖逐元素:

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

与 GPU SIMT 对比

SIMD(CPU)SIMT(GPU)
线程模型少量宽向量 lane大量轻量线程
分支全 lane 执行相同 opwarp 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 循环

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 特性:

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 底座;第二章 数学 用 向量与矩阵 重述同一物理过程。两章对照读:bytes 是存储,FLOPs 是计算,带宽是瓶颈——三个量构成工程三角。SIMD 与 GPU 并行互补,构成现代 AI 算力底座。

下一篇:矩阵为什么连续存储——行优先与 stride 如何服务 SIMD/GPU。

本页目录