第2章:计算基础与数据流——计算机体系结构回顾

各位同学,咱们今天聊聊计算机体系结构里最基础、也最绕不开的两个概念:冯·诺依曼架构和哈佛架构。说实话,我当年刚入行时,觉得这东西就是背背定义,直到后来做AI芯片映射,才发现这些底层结构直接决定了你的算子能跑多快。

2.1 冯·诺依曼与哈佛架构:存储与指令的博弈

冯·诺依曼架构,说白了就是「指令和数据共用一套存储总线」。你想想看,CPU取指令和读写数据都挤在同一条路上,这就带来了著名的「冯·诺依曼瓶颈」。我在做早期的CNN加速器时,就遇到过这种情况:卷积核权重加载和指令读取互相抢带宽,导致计算单元经常空等。

哈佛架构呢?它把指令存储和数据存储分开了,各走各的路。这样取指令和访存就能并行进行。嗯,这里要注意:现代很多DSP和嵌入式处理器都采用改进型哈佛架构——指令和数据物理上分开,但可以共享一部分地址空间。

核心区别一句话总结:冯·诺依曼是单车道,哈佛是双车道。AI芯片里,我们经常混合使用这两种思想。

我个人习惯在算子映射时先画一张「访存冲突图」:哪些数据是频繁读取的?哪些指令是循环执行的?如果指令和数据有严重冲突,我会优先考虑哈佛式的分离设计。

2.2 数据流驱动计算模型:让数据自己找路

传统的控制流计算是「指令驱动」——程序计数器告诉你下一步做什么。数据流计算反过来:数据准备好了,计算就自动触发。这有点像流水线上的工人:零件到了,你手里的活就开始干,不用等老板喊你。

我在做稀疏矩阵运算时,特别喜欢数据流模型。为什么呢?因为稀疏矩阵里很多元素是0,传统控制流要一条条指令判断「是不是0」,浪费大量时钟周期。数据流模型下,只有非零数据才会触发计算,效率直接翻倍。

数据流模型有几个关键特征:

  • 数据驱动:操作数就绪即执行,没有显式的指令顺序
  • 无副作用:每个计算节点只依赖输入,不修改全局状态
  • 天然并行:只要数据依赖允许,所有节点可以同时运行
避坑指南:我曾经在映射Transformer的Attention层时,试图用纯数据流模型。结果发现softmax的归一化操作需要全局最大值,这破坏了数据流的局部性。后来我改用混合模型——局部计算用数据流,全局归约用控制流。

2.3 SIMT与SIMD架构对比:单指令多数据的两种面孔

SIMD(单指令多数据)和SIMT(单指令多线程)经常被混为一谈,其实差别很大。我画个图帮你理解:

SIMD 架构 指令:ADD 数据A: [1,2,3,4] 数据B: [5,6,7,8] 结果: [6,8,10,12] 特点:所有处理单元执行相同指令 数据宽度固定(如128位) 适合规整的向量运算 SIMT 架构 指令:ADD T0:1 T1:2 T2:3 T3:4 T0:5 T1:6 T2:7 T3:8 T0:6 T1:8 T2:10 T3:12 特点:每个线程独立执行 但同一warp内指令相同 支持分支和掩码执行

SIMD就像军训队列——所有人同时做同一个动作,步调完全一致。SIMT更像一个班级——大家听同一个老师讲课(同一指令),但每个人可以有自己的笔记本(私有寄存器),有人可以举手问问题(分支)。

我拿矩阵乘法举个例子:

// SIMD 风格:数据打包
__m128 a = _mm_load_ps(&A[i]);
__m128 b = _mm_load_ps(&B[j]);
__m128 c = _mm_mul_ps(a, b);  // 4个乘法同时完成

// SIMT 风格:线程独立
int tid = threadIdx.x;
float result = A[tid] * B[tid];  // 每个线程算自己的

在实际AI芯片中,GPU用的是SIMT,而很多NPU用的是SIMD。为什么?GPU需要处理各种不规则的分支(比如不同像素的着色器),SIMT更灵活。NPU处理的是规整的卷积、全连接,SIMD的确定性更高,硬件开销更小。

注意:SIMT的「线程」是硬件线程,不是软件线程。切换开销极低(通常1-2个时钟周期)。但分支发散会导致部分线程被掩码,利用率下降。我在优化BERT模型时,就遇到过因为LayerNorm中的条件判断导致warp利用率从100%掉到60%的情况。

2.4 三种模型的融合:现代AI芯片的实践

你可能会问:这三种模型到底选哪个?我的答案是:全都要。现代AI芯片往往是混合体。

架构特征 冯·诺依曼 哈佛 数据流
指令存储 与数据共享 独立 隐含在数据依赖中
并行粒度 指令级 指令级+访存并行 数据级+任务级
典型应用 通用CPU DSP、MCU 脉动阵列、稀疏加速器
AI芯片中的角色 控制核、调度 权重存储、指令缓存 计算核心(如Systolic Array)

举个例子,Google的TPU:控制部分用冯·诺依曼(取指令、调度),权重存储用哈佛思想(独立SRAM),计算核心用数据流(脉动阵列里数据自动流动)。你看,三个模型在一个芯片里和谐共处。

我的经验:做算子映射时,先判断算子的计算模式。如果是规整的矩阵运算,优先考虑数据流+SIMD;如果是不规则的计算(比如Gather、Scatter),用SIMT更合适。别死磕一种模型,灵活组合才是王道。

好了,这一章的内容就到这里。记住:体系结构不是死板的教条,而是你手里的工具箱。遇到不同的算子,拿出合适的工具来用就行。

—— 一个在AI芯片坑里摸爬滚打多年的工程师

可可精品资料库