1. AI芯片概览与缓存架构基础

大家好,我是你们的讲师。今天咱们聊聊AI芯片的“家底”——从发展历程到缓存架构,把这些基础打牢了,后面讲算子融合、访存优化,你才能听得有滋味。

说实话,我入行那会儿,AI芯片还是个冷门方向。那时候大家做深度学习,基本靠GPU硬扛。现在呢?NPU、TPU、各种定制加速器遍地开花。这十年变化,比过去半个世纪都大。

1.1 AI芯片发展历程:从通用到专用

AI芯片的演进,说白了就是一条“从通用走向专用”的路。

  • CPU时代(2010年前):用CPU跑神经网络?可以,但慢得让人抓狂。我记得当时跑一个简单的MNIST识别,要等好几分钟。CPU强在控制逻辑和串行计算,但AI计算需要的是大规模并行。
  • GPU崛起(2012-2016):AlexNet在GPU上训练,一下子把图像识别准确率拉高了一大截。GPU的SIMT架构天生适合矩阵运算。我那时候第一次用CUDA写卷积,感觉就像打开了新世界的大门。
  • 专用芯片爆发(2017至今):Google TPU、华为昇腾、寒武纪NPU……这些芯片把AI计算做到了极致。它们砍掉了图形渲染、通用计算这些“累赘”,专门为矩阵乘法和卷积优化。

核心观点:AI芯片的进化,本质上是“计算效率”和“数据搬运”之间的博弈。越专用的芯片,数据搬运的代价就越低。

1.2 主流架构对比:GPU vs TPU vs NPU

这三种架构,我工作中都接触过。它们各有各的脾气。

维度 GPU(NVIDIA A100) TPU(Google v4) NPU(华为昇腾910)
计算核心 数千个CUDA Core 脉动阵列(Systolic Array) Da Vinci Core(3D Cube)
存储层次 L1/L2/显存(HBM) 统一缓冲区(UB)+ 显存 L0/L1/L2 + DDR
典型应用 训练为主,推理也可 推理为主,训练也可 训练+推理
编程模型 CUDA/OpenCL TensorFlow/XLA CANN/昇思

你可能会问:“这三种架构,哪个最好?” 嗯,这个问题没有标准答案。我个人习惯是:训练用GPU,推理用TPU或NPU。为什么?GPU的通用性最好,生态最成熟;TPU/NPU在推理场景下,能效比能高出好几倍。

避坑指南:我曾经在一个推理项目中,用GPU跑BERT模型,功耗飙到300W。后来换成NPU,同样的吞吐量,功耗只有80W。所以,选架构一定要看场景。

1.3 缓存层次结构:L1/L2/L3的基本概念

缓存,说白了就是“离计算单元最近的数据仓库”。为什么需要缓存?因为DRAM太慢了。你想想看,计算单元执行一条指令只要1纳秒,但从DRAM取数据要100纳秒。这中间的差距,全靠缓存来填。

典型的AI芯片缓存层次是这样的:

  • L1缓存:每个计算核心私有,容量最小(几十KB),速度最快(1-2个时钟周期)。我习惯把最常复用的数据放L1,比如卷积核的权重。
  • L2缓存:多个核心共享,容量中等(几MB),速度稍慢(10-20个时钟周期)。适合存放中间结果,比如特征图。
  • L3缓存:所有核心共享,容量最大(几十MB),速度最慢(几十个时钟周期)。通常用来做数据预取和缓冲。

下面这张图,是我自己画的AI芯片缓存架构示意图,帮你理清数据流动的路径。

AI芯片缓存层次结构示意图 Core 0 Core 1 Core 2 Core 3 L1 Cache (私有) L1 Cache (私有) L1 Cache (私有) L1 Cache (私有) L2 Cache (共享,几MB) L3 Cache (共享,几十MB) DRAM (HBM/DDR) 速度:快 → 慢 容量:小 → 大 成本:高 → 低

从这张图你能看到,数据从DRAM一路往上搬运到L1,再到计算核心。每一级缓存都在“过滤”数据访问,减少对慢速存储的依赖。

注意:缓存不是越大越好。L1太大,访问延迟会变高;L2太大,芯片面积会失控。我见过一个团队,为了追求缓存命中率,把L2做到32MB,结果芯片面积超标,成本翻了一倍。所以,缓存设计是个平衡艺术。

1.4 缓存一致性:多核协作的基石

多核芯片里,每个核心都有自己的L1缓存。如果Core 0修改了某个数据,Core 1的L1里还存着旧值,那计算就全乱套了。这就是缓存一致性问题。

常见的解决方案有两种:

  • 监听协议(Snooping):每个核心监听总线上的写操作,发现自己的缓存被改了,就标记为无效。适合核心数少的场景。
  • 目录协议(Directory):用一个中央目录记录每个数据块在哪个核心的缓存里。写操作时,只通知相关的核心。适合核心数多的场景。

我个人更倾向于目录协议。为什么?因为AI芯片的核心数动不动就几十上百,监听协议的总线带宽根本扛不住。我在做某款NPU时,就用了目录协议,虽然设计复杂度高一些,但扩展性好得多。

1.5 实战视角:缓存对算子性能的影响

说了这么多理论,咱们来点实际的。假设你要优化一个矩阵乘法算子,数据是1024x1024的浮点数。如果不考虑缓存,直接按行×列计算,你会发现L1缓存命中率极低——因为每次访问的数据都不在缓存里。

怎么优化?分块(Tiling)。把大矩阵切成小块,让每个小块的数据都能塞进L1缓存。这样,计算时数据就在缓存里,不用反复去DRAM搬。

// 伪代码:矩阵乘法的分块优化
for (int i = 0; i < N; i += BLOCK_SIZE) {
    for (int j = 0; j < N; j += BLOCK_SIZE) {
        for (int k = 0; k < N; k += BLOCK_SIZE) {
            // 将A[i:i+BLOCK_SIZE][k:k+BLOCK_SIZE]加载到L1
            // 将B[k:k+BLOCK_SIZE][j:j+BLOCK_SIZE]加载到L1
            // 计算C[i:i+BLOCK_SIZE][j:j+BLOCK_SIZE] += A_block * B_block
        }
    }
}

这段代码看起来简单,但BLOCK_SIZE的选择很有讲究。我建议你根据L1缓存大小来算:比如L1是32KB,每个浮点数4字节,那BLOCK_SIZE最大就是sqrt(32KB / 4B) ≈ 90。实际中我一般取64,留点余量给其他数据。

核心要点:缓存架构不是纸上谈兵。你写的每一行算子代码,最终都要和缓存层次打交道。理解缓存,就是理解性能的“命门”。

好了,这一章的内容就到这里。缓存架构是AI芯片的基石,后面讲算子融合、数据流优化,都会反复用到这些概念。希望你能把这张图、这些表格记在心里。