1. AI芯片软硬件协同设计的核心逻辑
1.1 为什么软硬件必须一起设计
做AI芯片这行的人都有一个共识:芯片设计不再是单纯的硬件活儿了。十年前做芯片,硬件团队把架构定下来,软件团队再往上适配,这种串行模式在传统CPU时代还能凑合,但到了AI芯片领域,这套玩法直接失效。
原因很直接。AI芯片的性能天花板不取决于峰值算力,而取决于数据在存储和计算单元之间的搬运效率。一个典型的矩阵乘法,如果数据搬运的时间比计算时间还长,那算力标称再高也是白搭。这就意味着,硬件架构的选择会直接决定软件怎么写才能跑得快,而软件的实际访问模式又会反过来影响硬件该配多大的缓存、多宽的带宽。
我见过不少团队踩过的坑:硬件团队闷头做了一版架构,理论算力很漂亮,结果软件团队拿到手发现,算子映射上去之后实际利用率不到30%。回头改硬件?流片费用摆在那里,改一版就是几千万的投入。所以现在行业里的主流做法是软硬件协同设计,从架构定义阶段就让软件团队介入,用模拟器跑真实模型,用真实模型的性能数据来指导硬件参数调整。
1.2 协同设计的三个层次
软硬件协同设计不是一句口号,它具体落地的时候分三个层次:
第一个层次是架构探索阶段。这个阶段硬件还没有RTL,软件也没有完整的编译器。大家靠的是性能模拟器和算子级建模。硬件团队提出几种候选架构方案,软件团队把主流模型(比如ResNet、BERT、Transformer类结构)映射上去,看每种方案的实际吞吐和能效。这个阶段的关键产出是架构参数的选择依据,比如PE阵列规模、片上缓存大小、片间互联带宽。
第二个层次是指令集与编译器协同。架构定下来之后,指令集的设计就成了核心矛盾点。指令太复杂,硬件解码逻辑臃肿,功耗上去了;指令太简单,编译器要生成的指令条数暴增,取指带宽又成了瓶颈。这个阶段需要软件团队和硬件团队坐在一起,把高频算子拆开看,哪些操作值得做成一条指令,哪些交给编译器组合生成就行。
第三个层次是部署验证阶段。芯片回来之后,真实的模型跑上去,性能数据和模拟器阶段往往有偏差。这时候需要定位偏差来源:是模拟器建模不准,还是编译器调度策略有问题,还是硬件本身有bug。这个阶段的排查经验非常宝贵,但往往只存在于团队内部文档里,外面的人很难学到。
1.3 一个典型的协同设计流程
我拿一个实际项目做例子,把流程串一遍。假设我们要做一颗面向边缘推理的AI芯片,目标场景是智能摄像头,主要跑目标检测和轻量级分类模型。
第一步,确定设计约束。功耗上限3W,成本控制在合理范围,推理延迟要求单帧不超过30ms。这些约束直接决定了我们不能堆太多PE,也不能用太大的片上SRAM。
第二步,模型分析。把目标场景下的主流模型拉出来,统计算子类型分布、参数量、激活值大小、内存访问模式。这一步的产出是一张算子频率表,比如卷积占比多少、深度可分离卷积占比多少、有没有全连接层、需不需要支持Transformer结构。
第三步,架构方案对比。基于算子频率表,硬件团队提出两到三种PE阵列方案,软件团队用模拟器跑性能。对比维度包括:峰值算力利用率、能效比、面积开销、编译难度。这里有个经验:不要只看峰值算力,要看目标模型下的实际有效算力。
第四步,指令集与编译器联合设计。架构方案确定后,开始定义指令格式。这个阶段软件团队要写大量的测试用例,验证编译器能否高效生成目标代码。硬件团队则要根据编译器的反馈调整指令编码,减少解码复杂度。
第五步,RTL实现与仿真验证。硬件团队写RTL,软件团队继续完善编译器和运行时。这个阶段要跑大量的回归测试,确保编译器生成的代码在RTL仿真下功能正确。
第六步,流片与硅后验证。芯片回来后,跑真实模型,对比模拟器数据和实测数据,定位偏差,优化编译器和运行时调度策略。
这个流程走下来,通常需要12到18个月。时间主要花在架构探索和联合调试上,真正写RTL的时间反而不算最长。
2. 硬件架构的关键设计点
2.1 计算阵列的设计取舍
AI芯片的计算阵列设计,核心是在灵活性和效率之间找平衡。常见的架构有三类:
脉动阵列是谷歌TPU带火的架构。它的特点是数据在PE之间流动,每个PE只做乘加运算,控制逻辑非常简单。优点是能效比高,缺点是灵活性差,遇到非规则计算模式(比如稀疏矩阵、动态shape)效率会骤降。脉动阵列适合卷积和矩阵乘法密集的场景,但如果模型里有大量逐元素操作或者控制流,就不太友好。
SIMD/SIMT阵列更接近GPU的思路。每个PE可以独立执行指令,灵活性高,能处理各种算子。但控制逻辑复杂,面积和功耗开销大。适合模型结构多变、算子类型丰富的场景。
可重构数据流架构是近几年比较热的方向。它试图在脉动阵列的效率和SIMT的灵活性之间找折中,通过配置PE之间的连接关系来适应不同的计算模式。这种架构的设计难度在于编译器的映射算法,要把计算图高效地切分并映射到PE阵列上,是个NP难问题。
我个人的经验是,没有最好的架构,只有最适合目标场景的架构。做架构选型之前,一定要先把目标模型分析清楚。如果目标场景就是跑固定的几个模型,脉动阵列可能是最优解;如果目标场景模型迭代快、算子类型多,那就要在灵活性上多留余量。
2.2 存储层次的设计
AI芯片的存储层次设计,比计算阵列更考验功力。因为大部分功耗花在数据搬运上,而不是计算本身。有数据显示,在28nm工艺下,一次32位浮点乘加的功耗大约是3pJ,而从DRAM读一次数据的功耗是它的几十倍甚至上百倍。
存储层次的设计核心是让数据离计算单元尽可能近。典型的层次包括:寄存器文件、片上SRAM、片外DRAM。有些架构还会加一级分布式缓存或者暂存器。
寄存器文件离PE最近,带宽最高,但面积开销大。片上SRAM容量大一些,但访问延迟和功耗都高于寄存器。片外DRAM容量最大,但带宽受限、功耗高。
设计的关键是确定每一级存储的容量和带宽配比。这个配比不是拍脑袋定的,要根据目标模型的数据复用模式来算。比如卷积运算,输入特征图和权重都有很高的复用率,那就需要足够的片上缓存来hold住这些数据,减少对DRAM的访问。
这里有个实操技巧:用roofline模型来指导存储设计。把目标模型的算术强度和硬件的带宽拐点画在一张图上,看看模型落在哪个区域。如果是带宽受限,那就优先加带宽;如果是算力受限,那就优先加PE。
2.3 数据流与互联设计
数据流设计决定了数据在计算阵列中怎么流动。常见的数据流有权重固定、输出固定、行固定等。不同的数据流对存储带宽的需求不同,对PE利用率的影响也不同。
权重固定的数据流,权重预先加载到PE中,输入特征图在阵列中流动。这种数据流适合卷积层,因为权重可以复用很多次,减少了权重加载的带宽需求。但缺点是如果权重太大,PE的本地存储放不下,就需要频繁换入换出。
输出固定的数据流,输出特征图的部分和累加结果保留在PE中,输入和权重在阵列中流动。这种数据流适合全连接层,因为输出维度通常很大,部分和需要累加很多次。
互联设计是另一个关键点。PE之间的连接方式决定了数据能否高效地在阵列中广播和收集。常见的互联有网格状、树状、环形等。网格状互联简单,但广播效率低;树状互联广播效率高,但面积开销大。
我踩过的一个坑是:互联带宽和PE算力不匹配。PE阵列算力很强,但互联带宽不够,数据供不上,PE大量时间在空转。后来我们调整了互联拓扑,增加了广播通道,PE利用率从不到50%提升到了80%以上。
3. 软件栈的设计与实现
3.1 编译器架构
AI芯片的编译器,和传统CPU编译器有本质区别。CPU编译器主要做指令调度和寄存器分配,而AI编译器要做的是计算图优化和算子映射。
典型的AI编译器架构分三层:
前端负责解析模型。输入通常是ONNX、TensorFlow GraphDef或者PyTorch的TorchScript。前端把模型转换成编译器内部的中间表示(IR)。这个IR通常是计算图形式,节点是算子,边是张量。
中端做图优化。包括算子融合、常量折叠、死代码消除、内存规划等。算子融合是AI编译器最关键的优化之一。比如把Conv+BN+ReLU融合成一个算子,减少中间结果的写回和读取,能显著降低内存带宽需求。
后端做算子映射和代码生成。把优化后的计算图映射到硬件上,生成指令序列或者配置数据。后端要考虑PE阵列的利用率、存储层次的数据分配、指令调度的并行性等。
这里有个设计难点:算子融合的粒度。融合得太少,内存访问开销大;融合得太多,寄存器压力大,可能导致溢出。我见过一些编译器为了追求融合,把整个模型融合成一个巨型算子,结果寄存器根本不够用,性能反而下降。
3.2 运行时与调度
运行时负责在芯片上执行编译器生成的代码。它的核心任务是任务调度和内存管理。
任务调度要考虑计算任务之间的依赖关系,以及硬件资源的可用性。比如多个算子可以并行执行的时候,运行时要把它们分配到不同的PE组上。如果算子之间有数据依赖,运行时就要保证执行顺序。
内存管理是运行时的另一个核心任务。AI芯片的片上存储通常很小,模型的数据放不下,需要分块加载。运行时要把张量切分成块,按需加载到片上,计算完再写回。这个过程中,内存分配策略直接影响性能。如果分配不当,会出现内存碎片,导致大块张量无法加载。
我实际调试的时候发现,运行时的调度开销经常被低估。一个算子执行可能只要几微秒,但调度决策本身可能就要花掉几微秒。如果算子粒度太细,调度开销占比就会很高。解决办法是算子融合和批处理,把多个小算子合并成一个大任务,减少调度次数。
3.3 算子库与手写优化
编译器自动生成的代码,性能往往不是最优的。对于高频算子,通常需要手写优化。
手写优化的核心是针对硬件特性做定制。比如矩阵乘法,编译器生成的代码可能是通用的三重循环,但手写优化可以利用PE阵列的广播特性、双缓冲技术、寄存器分块等技巧,把性能提升几倍。
手写优化的难点在于维护成本高。硬件架构一变,手写代码就要重写。所以通常的做法是:编译器生成通用代码保证功能正确,手写优化只针对最关键的几个算子,而且要用模板化或者代码生成的方式减少维护工作量。
我个人的经验是,先让编译器跑通,再针对热点算子做手写优化。不要一上来就手写所有算子,那样开发周期太长,而且很多算子其实不是性能瓶颈。
4. 软硬件联合调试与性能优化
4.1 性能建模与仿真
在芯片回来之前,性能建模是唯一的性能评估手段。建模的精度直接决定了架构决策的质量。
性能建模通常分两个层次:算子级建模和系统级建模。
算子级建模关注单个算子在硬件上的执行时间。需要建模的因素包括:PE阵列的并行度、存储访问延迟、指令发射开销等。算子级建模的精度要求高,因为它是系统级建模的基础。
系统级建模关注整个模型在硬件上的端到端性能。需要建模的因素包括:算子之间的依赖关系、数据传输开销、运行时调度开销等。系统级建模通常用离散事件仿真或者队列模型来实现。
我踩过的一个坑是:仿真模型过于乐观。仿真的时候假设存储访问零延迟、PE利用率100%,结果实测性能只有仿真的一半。后来我们在仿真模型里加入了更真实的延迟模型和资源竞争模型,仿真精度才提上来。
4.2 硅后调试与性能分析
芯片回来之后,调试才是真正的挑战。硅后调试的核心任务是定位性能瓶颈。
常用的性能分析手段包括:
硬件性能计数器。在芯片里埋一些计数器,统计PE利用率、存储访问次数、指令发射数等。这些数据能帮你快速定位瓶颈在计算还是存储。
指令级追踪。记录芯片执行的指令序列,分析指令之间的依赖关系和流水线停顿。这个手段能帮你发现编译器生成代码的问题。
内存访问分析。统计各级存储的访问频率和命中率,看看数据搬运是否合理。
我实际调试的时候,最有效的手段是对比分析。把实测数据和仿真数据放在一起对比,看偏差出现在哪个环节。如果某个算子的实测时间远大于仿真时间,那就要深入分析这个算子的执行过程,看看是PE利用率低,还是存储访问冲突,还是指令调度有问题。
4.3 常见性能问题与解决思路
在AI芯片的软硬件调试中,有几类性能问题特别常见:
PE利用率低。原因可能是数据供不上、指令调度不合理、算子映射不均衡。解决办法包括:优化数据流、调整指令调度策略、改进算子切分算法。
存储带宽瓶颈。原因可能是片上缓存太小、数据复用率低、访问模式不连续。解决办法包括:增加片上缓存、优化数据分块策略、改进数据布局。
运行时开销大。原因可能是算子粒度太细、调度算法复杂、内存分配频繁。解决办法包括:算子融合、批处理、预分配内存。
编译器生成代码质量差。原因可能是IR设计不合理、优化pass不够、后端映射算法简单。解决办法包括:改进IR、增加优化pass、手写关键算子。
这些问题往往不是孤立的,一个问题的解决可能会引入另一个问题。比如为了提升PE利用率,增加了数据预取,结果存储带宽不够了。所以性能优化是一个迭代过程,需要不断测量、分析、调整。
5. 实操建议与避坑指南
5.1 架构探索阶段的注意事项
架构探索阶段最容易犯的错误是过早优化。有些团队一上来就追求极致的能效比,把PE阵列设计得非常专用,结果模型一换,性能就崩了。
我的建议是:先保证通用性,再追求效率。第一版架构可以稍微保守一点,留足够的灵活性。等芯片回来,跑真实负载,看清楚瓶颈在哪里,下一版再针对性优化。
另一个注意事项是不要忽略编译器难度。有些架构硬件上很优雅,但编译器映射算法极其复杂,导致开发周期失控。架构设计的时候,一定要评估编译器的实现难度,必要时为了编译器友好而牺牲一点硬件效率。
5.2 编译器开发的实操心得
编译器开发最大的挑战是调试。编译器出的bug往往很隐蔽,生成的代码功能不对,但很难定位是哪一步优化引入的。
我的经验是:建立完善的测试体系。每写一个优化pass,都要有对应的测试用例。测试用例要覆盖各种边界情况,比如空张量、超大张量、特殊shape等。另外,保留中间表示,出问题的时候可以逐层对比,快速定位问题pass。
还有一个技巧是用参考实现做对比。编译器生成的代码,结果要和CPU上的参考实现对比。如果结果不一致,就用二分法定位问题pass。
5.3 硅后调试的避坑经验
硅后调试最怕的是问题无法复现。有些bug是偶发的,跑一百次才出现一次。这种问题往往和时序、资源竞争有关。
我的建议是:先复现,再定位。想办法让问题稳定复现,比如调整输入数据、改变执行顺序、增加压力测试。问题能稳定复现了,定位就成功了一半。
另一个经验是不要忽视软件问题。硅后调试的时候,大家容易先怀疑硬件。但实际上,很多问题出在软件上,比如编译器bug、运行时调度错误、驱动配置不对。所以调试的时候,软硬件要一起查,不要只盯着硬件。
5.4 团队协作的经验
软硬件协同设计对团队协作的要求很高。硬件团队和软件团队的思维方式不同,沟通起来容易有摩擦。
我的经验是:建立共同的度量标准。硬件团队关注面积、功耗、频率,软件团队关注吞吐、延迟、利用率。大家要用同一套指标来评估方案,比如目标模型下的端到端性能。这样讨论问题的时候,才有共同语言。
另外,定期做联合review。硬件团队讲架构方案,软件团队讲映射策略,大家一起讨论哪里可能有问题。这种review越早做越好,不要等到流片前才发现软硬件不匹配。
6. 从项目实践中总结的几个关键认知
6.1 没有银弹,只有取舍
AI芯片设计里没有完美的方案。脉动阵列效率高但不灵活,SIMT灵活但效率低,可重构架构折中但编译器难做。每个选择都有代价,关键是想清楚目标场景下哪个代价可以接受。
我见过一些团队,什么都想要,结果做出来的芯片什么都不突出。反而是那些明确目标场景、敢于在某些维度上妥协的团队,做出来的芯片更有竞争力。
6.2 软件是硬件的放大器
同一颗芯片,软件优化得好不好,性能差距可能有两三倍。编译器优化、算子手写、运行时调度,每一个环节都能显著影响最终性能。
所以做AI芯片,软件团队的投入不能省。有些团队硬件很强,软件凑合,结果芯片性能发挥不出来,市场表现很差。反过来,硬件一般但软件极强的团队,往往能做出超出预期的产品。
6.3 迭代比一步到位更现实
第一版芯片就想做到完美,几乎不可能。更现实的路径是:第一版跑通,第二版优化,第三版打磨。
第一版的目标是功能正确、性能可用。不要追求极致能效比,先把软硬件流程跑通,积累真实负载数据。第二版针对第一版暴露的瓶颈做优化,比如加带宽、改数据流、优化编译器。第三版再打磨细节,提升能效和面积效率。
这个迭代过程需要时间,但比一步到位更可控。我见过的成功项目,基本都是迭代出来的,没有哪个是一版就成的。
6.4 数据比直觉可靠
架构设计里有很多直觉判断,比如“这个算子应该很快”、“那个数据流应该很高效”。但实测数据往往和直觉不一样。
所以我的习惯是:任何决策都要有数据支撑。架构选型要有仿真数据,编译器优化要有性能对比,硅后调试要有性能计数器数据。直觉可以用来提出假设,但验证假设必须靠数据。
这个习惯让我避免了很多想当然的错误。比如有一次,我们觉得某个算子的瓶颈在计算,结果性能计数器显示瓶颈在存储访问。如果没有数据,我们可能就优化错方向了。
6.5 文档和工具链是长期资产
项目过程中积累的文档和工具链,往往比芯片本身更有长期价值。架构探索的仿真模型、编译器的测试用例、硅后调试的分析脚本,这些东西在下一个项目里还能复用。
我建议团队把工具链建设当成正式任务,不要当成副业。工具链完善了,下一个项目的开发效率会高很多。特别是性能建模工具和自动化测试工具,投入产出比很高。
最后分享一个小技巧:建立性能数据库。把每个项目、每个模型、每个算子的性能数据都记录下来,形成团队的知识库。下次做架构决策的时候,翻翻数据库,看看类似场景下的历史数据,能少走很多弯路。这个数据库不需要多复杂,一个结构化的表格就行,关键是坚持记录。