AI 芯片这个话题,聊架构、聊算力、聊制程的文章已经够多了,但真正落到"软硬件怎么配合设计"这个层面,能讲清楚的人并不多。我做了几年加速器相关的软硬件协同工作,从 RTL 仿真到编译器算子映射都趟过一遍,深知这里面的坑有多深。这篇就围绕 AI 芯片软硬件设计这个主题,把脉动阵列的基本原理、Transformer 这类模型在硬件上的映射逻辑、以及 TPUv1 当年为什么那么设计,掰开揉碎讲一遍。不管你是刚入行的数字 IC 设计工程师,还是做模型部署想理解底层硬件的算法同学,又或者只是对"AI 芯片到底难在哪"好奇的技术爱好者,这篇都能给你一些能直接用的东西。我会尽量少堆公式,多用类比和实际设计中的取舍来讲,因为软硬件协同这件事,本质上就是一连串"为什么这么选"的决策链。
1. 为什么 AI 芯片的软硬件必须一起设计
1.1 通用处理器跑神经网络到底卡在哪
先想一个问题:为什么不用现成的 CPU 或 GPU 直接跑神经网络,非要专门设计 AI 芯片?很多人第一反应是"CPU 算力不够",这个答案对,但太粗糙。真正的问题在于数据搬运的开销远大于计算本身。
我拿一个具体的例子算给你看。假设一个全连接层,输入维度 1024,输出维度 1024,权重矩阵就是 1024×1024,用 FP16 存储大约 2MB。每次推理要读这 2MB 权重,做大约 200 万次乘加运算。在 CPU 上,这 2MB 数据要从主存搬到缓存、再搬到寄存器,而 CPU 的算力单元其实大部分时间在等数据。这就是所谓的内存墙——算力增长快,但内存带宽增长慢,两者差距越拉越大。
GPU 缓解了一部分问题,因为它有几千个核心可以并行,吞吐上去了。但 GPU 的功耗和面积效率对专用场景来说仍然浪费,它的调度逻辑、缓存层级是为通用图形计算设计的,跑矩阵乘的时候有大量控制开销。AI 芯片的核心思路就是:把数据流设计成让计算单元尽量不空等,把控制逻辑砍到最简,把面积和功耗全砸在乘加阵列和片上存储上。
这就引出了软硬件协同设计的根本矛盾:硬件是固定的、并行的、有严格时序的;而模型是变化的、算子多样的、精度需求各异的。硬件设计者如果不懂模型的计算模式,就会设计出"看起来很猛但实际跑不满"的架构;算法工程师如果不懂硬件的数据流约束,就会写出"理论 FLOPs 很高但硬件利用率只有 20%"的模型。所以这两拨人必须坐在一起,从架构定义阶段就对齐。
1.2 软硬件协同的三个层次
我把协同设计分成三个层次来理解,这样你在实际工作中能快速定位自己处在哪一层。
第一层是架构层协同。这一层决定芯片的宏观结构:用什么样的计算阵列、片上缓存多大、片外带宽多少、支持哪些数据精度。这一层的决策一旦流片就无法更改,所以必须在设计前就把目标模型的计算特征摸清楚。比如你要跑 Transformer,那注意力机制里的矩阵乘维度变化很大,阵列就不能设计得太死板。
第二层是编译层协同。硬件定了之后,怎么把模型算子映射到硬件上,就是编译器的事。这一层要解决算子融合、数据切分、流水线调度、精度量化等问题。编译器做得好不好,直接决定硬件利用率。我见过同一个硬件,换个编译器,性能差三倍的情况。
第三层是运行时协同。实际部署时,batch size 会变、输入长度会变、多模型可能混跑,运行时调度要动态分配硬件资源。这一层最容易被忽视,但对实际体验影响巨大。
提示:很多团队把这三层割裂开,架构师画完框图就交给编译器团队,编译器团队做完就交给部署团队,结果每一层都在为上一层的"不合理设计"打补丁。真正高效的团队会让三个层次的人定期同步,甚至让编译器工程师参与架构评审。
1.3 一个反直觉的结论:算力不是最重要的指标
新手看 AI 芯片参数,第一眼盯的是 TOPS(每秒万亿次运算)。但实际部署中,有效算力才是关键,也就是在真实模型上能跑出来的算力。一块标称 256 TOPS 的芯片,如果跑某个模型只能达到 30 TOPS 的有效算力,那它还不如一块标称 64 TOPS 但利用率能到 80% 的芯片。
有效算力 = 标称算力 × 硬件利用率。而硬件利用率取决于数据复用做得够不够、访存瓶颈有没有卡住、算子映射是否匹配阵列形状。这些全是软硬件协同要解决的问题。所以我在评估一块 AI 芯片时,从来不只看纸面参数,而是会问:跑 ResNet-50 的利用率是多少?跑 BERT-base 呢?跑一个自定义的注意力层呢?这些数字才说明问题。
2. 脉动阵列:AI 芯片最经典的计算骨架
2.1 脉动阵列到底"脉动"在哪里
脉动阵列(Systolic Array)这个名字听起来玄乎,其实核心思想特别朴素:让数据像心跳一样有节奏地在计算单元之间流动,每个计算单元只做最简单的乘加,数据流过就出结果。
我打个比方。想象一条流水线,上面站着一排工人,每个工人手里拿着一个权重值。数据从左边一个个传进来,每经过一个工人就做一次乘法并累加,然后传给下一个工人。同时,权重从上往下传,数据从左往右传,两者在交叉点相遇就做一次乘加。这样,一个矩阵乘法的所有运算就能在数据流动的过程中完成,不需要反复从内存里取数。
这种设计的精妙之处在于:每个计算单元(PE)只需要一个乘法器、一个加法器、几个寄存器,没有复杂的地址计算,没有指令译码。控制逻辑极简,面积和功耗几乎全用在计算上。这就是为什么 TPUv1 能在那个年代做到那么高的能效比。
脉动阵列的"脉动"体现在数据是有节奏地、周期性地流动的。每个时钟周期,数据在阵列里前进一格,就像心脏跳动推动血液流动一样。这种规律性让时序设计变得简单,也让数据复用达到极致——一个权重值被加载进 PE 后,可以被多个输入数据复用,不需要重复访存。
2.2 数据流设计:权重固定还是输出固定
脉动阵列不是只有一种形态,根据数据在阵列里的流动方式,主要分几种数据流。这个选择直接决定硬件的访存模式和编译器的工作方式,是架构设计里最关键的决策之一。
| 数据流类型 | 权重流动 | 输入流动 | 输出流动 | 适用场景 |
|---|---|---|---|---|
| 权重固定(WS) | 静止 | 横向流动 | 纵向累加 | 权重复用高的卷积 |
| 输出固定(OS) | 横向流动 | 纵向流动 | 静止 | 大矩阵乘 |
| 行固定(RS) | 静止 | 对角流动 | 横向输出 | 平衡型设计 |
权重固定(Weight Stationary)是最常见的方案。权重预先加载到 PE 里就不动了,输入数据横向流过,部分和纵向累加。这种方案适合卷积神经网络,因为卷积核(权重)在整个特征图上反复使用,权重固定能最大化复用。
输出固定(Output Stationary)则是让输出部分和留在 PE 里不动,权重和输入都流动进来。这种方案适合大矩阵乘,因为输出累加不需要反复搬运,省了带宽。
实际芯片里往往是混合的。TPUv1 用的是权重固定为主的脉动阵列,256×256 的乘加阵列,专门为卷积和全连接层优化。但到了 Transformer 时代,矩阵乘的形态变了,纯权重固定的阵列就不一定最优了,这也是为什么后来的 AI 芯片开始支持更灵活的数据流配置。
2.3 阵列尺寸怎么定:一个真实的权衡过程
假设你要设计一个脉动阵列,尺寸定多大?256×256 还是 128×128?这不是拍脑袋决定的,要算账。
阵列面积大致和尺寸的平方成正比。256×256 的阵列有 65536 个 PE,每个 PE 假设占 1000 平方微米(含乘法器、加法器、寄存器),光阵列就占 65 平方毫米,这在很多工艺下已经是大芯片了。而 128×128 只有 16384 个 PE,面积降到四分之一。
但尺寸小意味着单次能处理的矩阵块小,需要更多次的数据加载和部分和累加,访存开销上升。这里有个经验公式:阵列尺寸应该匹配目标模型里最常见的矩阵维度。如果目标模型的主力矩阵乘是 512×512,那 256×256 的阵列刚好可以分四块处理,复用效率高;如果主力是 128×128,那 256 的阵列就有一半 PE 在空转。
我在实际项目中遇到过这个问题:一开始定了 256×256,结果发现目标模型里大量小矩阵乘,利用率只有 40%。后来改成 128×128 加多阵列并行,利用率提到 75%。所以阵列尺寸不是越大越好,匹配才是王道。
注意:阵列尺寸还受限于时钟频率。阵列越大,信号在阵列里传播的路径越长,时序收敛越难,频率就上不去。256×256 的阵列想跑到 1GHz 以上,在先进工艺下都很有挑战。很多时候降频换面积是划算的。
2.4 脉动阵列的软肋:不规则计算怎么办
脉动阵列最大的问题是它只擅长规则的矩阵乘和卷积。一旦遇到不规则的计算——比如注意力机制里的 softmax、LayerNorm、各种激活函数、稀疏操作——阵列就使不上劲了。
这些操作怎么办?通常是在阵列旁边挂一个向量处理单元(VPU),专门处理这些逐元素或归约操作。阵列负责矩阵乘这种"大块头",VPU 负责"零碎活"。软硬件协同在这里的体现就是:编译器要能识别哪些算子该丢给阵列,哪些该丢给 VPU,并且安排好两者的流水线,别让阵列等 VPU,也别让 VPU 等阵列。
我见过一个设计,阵列很强但 VPU 太弱,结果跑 Transformer 的时候,注意力里的 softmax 成了瓶颈,阵列大部分时间在等 VPU 算完。后来把 VPU 的并行度提高了一倍,整体性能提升了 40%。这个教训说明:木桶效应在 AI 芯片里特别明显,软硬件设计要均衡,不能有明显的短板。
3. Transformer 上硬件:比 CNN 难在哪
3.1 注意力机制的计算特征拆解
Transformer 的核心是自注意力(Self-Attention),它的计算可以拆成几步:输入分别乘以三个权重矩阵得到 Q、K、V;然后 Q 和 K 的转置做矩阵乘得到注意力分数;分数经过 softmax 归一化;最后和 V 做矩阵乘得到输出。
从硬件角度看,这里面有几种截然不同的计算模式。Q、K、V 的生成是标准的矩阵乘,脉动阵列很擅长。QK^T 也是矩阵乘,但维度是序列长度×序列长度,序列长度可变,从几十到几千都有可能。softmax 是归约操作,需要找最大值、算指数、求和、再归一化,这是 VPU 的活。最后和 V 相乘又是矩阵乘。
问题在于序列长度是动态的。CNN 的输入尺寸相对固定,硬件可以针对性地优化。但 Transformer 的序列长度在推理时可能变化,比如对话场景,输入长度每次都不一样。这就要求硬件和编译器能动态适应,不能写死。
3.2 序列长度动态变化带来的调度难题
假设你的脉动阵列是 128×128 的,处理 QK^T 时,如果序列长度是 128,刚好一次搞定;如果是 512,就要分四次;如果是 100,那就有 28 行 PE 在空转。这种维度不匹配导致的利用率下降是 Transformer 硬件加速的核心痛点。
解决办法有几种。一种是padding,把不足的维度补齐,简单但浪费算力。一种是动态分块,编译器根据实际序列长度选择最优的分块策略,这需要硬件支持灵活的数据加载。还有一种是多序列并行,把多个短序列拼在一起凑满阵列,但这要求注意力计算能正确处理序列边界,实现复杂度高。
我在实际项目里用过动态分块加多序列并行的组合方案。对于短序列,把多个 batch 的序列拼起来填满阵列;对于长序列,切成块做分块注意力。编译器里要维护一套复杂的调度逻辑,但换来的是平均利用率从 50% 提到 80% 以上。这套逻辑写起来很痛苦,但效果确实值。
3.3 KV Cache:推理场景的访存大头
Transformer 推理有个特殊之处:自回归生成时,每生成一个新 token,都要和之前所有 token 的 K、V 做注意力计算。为了避免重复计算,会把之前算好的 K、V 缓存起来,这就是KV Cache。
KV Cache 的大小 = 2 × 层数 × 序列长度 × 隐藏维度 × 精度字节数。以一个 12 层、隐藏维度 768、序列长度 2048、FP16 的模型为例,KV Cache 大约是 2×12×2048×768×2 字节,约 72MB。这个数字看着不大,但它是每生成一个 token 都要完整读一遍的。生成 1000 个 token,就要读 72GB 的数据。访存带宽成了绝对瓶颈。
所以现代 AI 芯片设计里,KV Cache 的管理和搬运是重中之重。硬件上要有大容量的片上缓存或者高带宽的片外存储接口;软件上要做 KV Cache 的分页管理、量化压缩、甚至选择性丢弃。这些全是软硬件协同的活。我见过一个优化案例,把 KV Cache 做 INT8 量化,精度损失很小,但访存带宽直接减半,推理吞吐翻倍。
3.4 从 CNN 到 Transformer:硬件设计的范式转移
CNN 时代的硬件设计思路是"权重复用最大化",因为卷积核小且反复使用。Transformer 时代,权重矩阵大得多,而且注意力计算里 Q、K、V 都是动态生成的,没有固定的"权重"可以长期驻留。
这意味着硬件设计要从权重固定转向更灵活的数据流。有些新架构开始支持可重构的互连,让 PE 之间的连接方式可以根据算子动态配置。还有些架构增加了专门的注意力加速单元,把 QK^T、softmax、AV 这条链路做成流水线。
TPUv1 那个年代还没有 Transformer,它的设计完全是为 CNN 优化的。后来 TPU 的迭代版本不断增加对 Transformer 的支持,比如更大的片上缓存、更灵活的向量单元、对 KV Cache 的专门优化。这个演进过程本身就是软硬件协同的活教材:模型在变,硬件必须跟着变,而硬件的变化又反过来影响模型的设计选择。
4. TPUv1 的设计哲学与当代启示
4.1 TPUv1 的核心参数与设计取舍
TPUv1 是 2015 年左右的设计,现在看参数不算亮眼,但它的设计思路至今仍有参考价值。它有一颗 256×256 的脉动阵列,峰值算力 92 TOPS(INT8),片上 24MB 的统一缓存,用 DDR3 做片外存储。
注意几个关键取舍。第一,它只做推理不做训练,所以不需要支持反向传播,不需要存梯度,硬件可以大幅简化。第二,它主打 INT8 量化,牺牲一点精度换能效和面积。第三,它用 DDR3 而不是更贵的 HBM,因为推理场景对带宽的需求相对可控,用便宜的内存降低整体成本。
这些取舍背后是清晰的目标定位:在数据中心里以低成本、高能效地跑推理。它不追求训练能力,不追求最高精度,不追求最先进的内存。这种"有所不为"的设计哲学,比堆参数更难,也更有价值。
4.2 指令集与编译器的配合
TPUv1 的指令集非常精简,主要就是几条:读权重、读输入、矩阵乘、激活、写输出。这种精简指令集的好处是硬件译码简单,但代价是编译器要做大量工作。
编译器要把高层模型拆成这些基本指令的序列,还要安排数据在片上缓存和阵列之间的搬运。TPUv1 的编译器有个关键设计:它把整个模型编译成一个指令序列,而不是运行时动态调度。这样运行时开销极小,但灵活性差,模型一变就要重新编译。
这种"静态编译"的思路在推理场景很合适,因为推理的模型和输入形状相对固定。但到了 Transformer 这种动态性强的场景,静态编译就不够用了,需要更动态的调度。这也是为什么后来的 AI 芯片编译器越来越复杂,要在静态优化和动态适应之间找平衡。
4.3 从 TPUv1 看软硬件协同的边界
TPUv1 的成功很大程度上是因为它明确了自己的边界:只做推理、只做矩阵乘为主的计算、只支持有限的精度。在这个边界内,软硬件可以深度协同,把效率做到极致。一旦超出边界,性能就断崖式下跌。
这对我们的启示是:设计 AI 芯片时,明确"不做什么"比"做什么"更重要。你想支持所有模型、所有精度、所有场景,结果就是什么都不精。反过来,如果你能清晰定义目标场景,软硬件就能围绕这个场景做深度优化。
当然,现实中的芯片往往要兼顾多个场景,那就需要在架构上做模块化设计,让不同场景用不同的硬件单元,编译器负责调度。但无论如何,边界清晰是高效协同的前提。
5. 软硬件协同中的实操经验与避坑指南
5.1 精度量化:软硬件必须一起定
量化是 AI 芯片绕不开的话题。把 FP32 降到 INT8,算力翻几倍,功耗降一半,但精度会掉。掉多少?这取决于模型、取决于量化策略、也取决于硬件的量化单元怎么设计。
我踩过的一个坑:硬件团队自己定了量化方案,用对称量化、per-tensor 缩放。结果算法团队拿过去一跑,某些层的精度掉得厉害,因为那些层的权重分布不对称,per-tensor 缩放根本不够。后来改成 per-channel 非对称量化,精度回来了,但硬件要支持更复杂的缩放计算,面积增加了一些。
这个教训是:量化方案必须软硬件一起定。算法团队要告诉硬件团队,哪些层对精度敏感,需要更细粒度的量化;硬件团队要告诉算法团队,支持哪些量化模式,代价是多少。双方在架构定义阶段就要对齐,不能各干各的。
5.2 算子融合的边界在哪里
算子融合是编译器优化的常用手段,把多个算子合并成一个,减少中间结果的访存。比如 Conv + BN + ReLU 融合成一个,中间结果不用写回内存再读出来。
但融合不是越多越好。融合后的算子可能超出硬件的单次处理能力,需要拆分;融合可能改变数值精度,需要重新验证;融合可能让调度变得复杂,反而降低利用率。我见过一个过度融合的案例,把十几个算子融成一个巨大的 kernel,结果寄存器不够用,频繁 spill,性能反而下降。
合理的做法是:优先融合访存密集的相邻算子,对计算密集的算子保持独立。具体融合到什么程度,要用 profiler 实测,看融合前后的访存量和利用率变化。这个边界因硬件而异,没有通用答案。
5.3 编译器与硬件的"契约"
编译器和硬件之间需要一份清晰的"契约":硬件提供哪些指令、哪些寄存器、哪些内存空间、时序约束是什么;编译器承诺生成符合这些约束的代码。这份契约如果模糊,就会出现编译器生成的代码跑不对、或者跑得慢的情况。
我在项目里坚持做的一件事是:维护一份硬件抽象层的精确文档,并且用自动化测试保证编译器和硬件的一致性。每次硬件改动,都要跑一遍回归测试,确保编译器生成的代码仍然正确。这个投入看起来大,但比后期调试省事得多。
提示:如果你的团队还没有硬件抽象层的文档,建议现在就补。哪怕只是一份简单的指令列表和寄存器映射表,也能省下大量沟通成本。
5.4 性能分析:别只看端到端数字
优化性能时,很多人只看端到端的延迟或吞吐。这个数字当然重要,但它不告诉你瓶颈在哪。要定位瓶颈,必须做细粒度的性能分析:每个算子的耗时、每次访存的带宽利用率、阵列的活跃周期比例。
我常用的方法是分层 profiling:先看端到端,找出最慢的几个算子;再看这些算子的内部,是计算受限还是访存受限;如果是访存受限,再看是哪一级存储的带宽不够。这样一层层往下挖,才能找到真正的瓶颈。
有个案例:端到端看,某个模型推理慢,怀疑是阵列不够强。但 profiling 发现,阵列利用率其实有 70%,真正的问题是片外带宽跑满了,数据供不上。解决办法不是换更大的阵列,而是优化数据复用,把更多数据留在片上。如果只看端到端数字,很容易做出错误的优化决策。
6. 写在最后的一些个人体会
做 AI 芯片软硬件协同这些年,我最大的体会是:这不是一个纯技术问题,而是一个沟通和权衡问题。硬件工程师、编译器工程师、算法工程师,三拨人的语言体系不一样,关注点不一样,很容易各说各话。能把这三拨人拉到一张桌子上,用共同的语言讨论问题,比任何单点技术突破都重要。
另一个体会是:不要迷信纸面参数,要相信实测数据。TOPS 再高,利用率上不去也是白搭。我在评估任何方案时,都会先跑一个真实模型,看实际性能,再决定要不要深入。这个习惯帮我避开了很多"看起来很美"的坑。
最后,AI 芯片这个领域变化太快,今天的最优解明天可能就过时了。保持学习,保持对模型演进的敏感,保持和一线工程师的交流,比死守某个架构方案更重要。Transformer 之后还会有新的架构,硬件设计要留出足够的灵活性去适应,而不是把宝全押在某一种计算模式上。