SCALE-Sim源码深度评测:脉动阵列AI加速器仿真器的工程实践
2026/9/14 8:27:44 网站建设 项目流程

一行命令都没跑,先花三天把 SCALE-Sim 的源码从 GitHub 上拉下来通读了一遍,这种“静态工程评测”的习惯我从看开源项目第一眼就养成了。SCALE-Sim 在 AI 加速器仿真领域是个绕不开的名字,ARM Research 主导开源的脉动阵列仿真器,GitHub 上那个仓库挂在 ARM-software 组织下面,项目描述写得也很直白:cycle-accurate simulation of systolic array based AI accelerators。简单说,它是一个专门用来模拟脉动阵列在 CNN 推理场景下性能表现的仿真工具,你可以定义阵列尺寸、数据流策略、片上存储大小,然后塞给它一个网络模型,它帮你算出来每一层的 cycle 数、MAC 利用率、DRAM 访问带宽需求这些硬指标。我这次做的评测主要针对源码工程结构、核心实现逻辑、不同版本之间的 API 差异和边界行为,同时也把 ARM 平台下的部署流程一起走了一遍。这篇文章适合两类人看:一类是做 AI 芯片架构评估的研究生和工程师,另一类是准备在自己的 ARM 开发板上跑仿真、想弄清楚 SCALE-Sim 到底能不能用的同学。

先说结论:SCALE-Sim 不是那种拿出来就能立刻给你一个“最优架构”答案的工具,它更像一个性能建模计算器。你给它配置好脉动阵列的基本几何参数和数据流策略,它就能模拟出运行某个网络各层的计算与访存行为,输出逐层的性能和带宽报告。这种工具的工程价值在于:在流片之前,你可以在宿主机上快速评估上百组架构配置,把那些明显不合理的参数组合先淘汰掉。我读完整个源码之后最大的感受是:这个项目的核心复杂度并不高,本质是一个带时序维度的 MAC 运算调度模拟器,真正的工程难点在于把数据流行为建模得足够细,以及把访存带宽的计算逻辑做到既简单又能反映硬件的真实瓶颈。

1. 项目源头与整体设计思路拆解

1.1 为什么 ARM 要开源一个脉动阵列仿真器

ARM 的处理器 IP 几乎覆盖了整个边缘计算设备,而 AI 加速器正是边缘计算中增长最快的部分。SCALE-Sim 这类仿真器的定位是在处理器架构设计的早期阶段提供性能和功耗评估,避免每次架构改动都要走一次 RTL 仿真或者 FPGA 原型验证。RTL 仿真的速度太慢,一个中等规模的 CNN 模型在 RTL 级别仿真跑完可能需要几天甚至几周,而 SCALE-Sim 在 CPU 上几分钟就能跑完。ARM 把这样一套仿真器开源出来,一方面是可以让研究者用统一标准来对比不同脉动阵列设计,另一方面也降低了生态门槛——整个工具链都是 Python 写的,比 SystemC 或者 Verilog 的仿真环境友好得多。

从源码的工程构成来看,SCALE-Sim 的设计思路非常清晰:用配置驱动模拟过程,把网络模型描述和硬件参数完全解耦。仿真器内部是一个事件驱动的循环,按 cycle 逐个推进,而不是按层逐个推进。这种设计的好处是,你可以在同一个时间轴上观察到不同计算部件的流水线行为,比如访存阶段和计算阶段的重叠。

1.2 脉动阵列的核心概念:数据流决定一切

脉动阵列的英文 systolic array,最早是卡内基梅隆大学在上世纪八十年代提出的,本质是通过让数据在阵列的 PE(Processing Element)之间规律性地流动,从而最大程度复用数据、减少访存次数。SCALE-Sim 支持的三种数据流策略决定了阵列中每个 PE 内部的 buffer 里存什么、流动的是什么。

  • 权重固定(WS,Weight Stationary):权重先全部装载到 PE 内部的寄存器文件中,激活值和部分和在阵列中流动。这种策略适合权重不能频繁换入换出的场景,通常在批量推理时效果较好。
  • 输出固定(OS,Output Stationary):部分和保留在 PE 内部累积,权重和激活值在阵列中流动。因为 CNN 卷积的输出通道数通常很多,部分和重用次数大,所以 OS 在很多架构中表现更优。
  • 输入固定(IS,Input Stationary):激活值固定在 PE 中,权重和部分和流动。这种策略更利于减少激活值的搬移。

我在实际评测这三种数据流在 SCALE-Sim 源码里的实现方式时发现,它们的核心区别体现在 process_tile 函数中数据 update 的阶段不同。WS 模式下,权重是在计算开始前预装的,而 OS 模式下激活值在每一行计算前需要更新。源码里用嵌套循环来模拟阵列里每个 PE 在一个 cycle 内能收到什么数据、能否执行 MAC,这也是我在后面静态评测中重点看的逻辑。

数据流策略在PE中固定的数据流动的数据典型适用场景
WS权重激活值、部分和权重复用率高的网络
OS部分和激活值、权重输出通道多、偏置计算频繁的网络
IS激活值权重、部分和激活值复用率高的网络

1.3 为什么选 Python 而不是 C/C++

这是一个很多人会问的问题。仿真器是性能敏感型工具,理论上用 C++ 写更快。但 SCALE-Sim 的场景是架构探索,它的目标是快速迭代和灵活修改,而不是追求仿真本身的速度极致。Python 的矩阵运算库 NumPy 可以高效处理大部分张量层面的计算,而真正的核心 MAC 调度逻辑在 Python 层面也跑得动——这在循环次数不超过十万次的仿真中完全够用。

更重要的是,学术界默认的工具链就是 Python + Jupyter Notebook,使用 Python 可以直接把仿真结果做可视化,降低后续数据分析的门槛。我在评价一个仿真工具的工程价值时,会关注它的可扩展性和易用性,SCALE-Sim 在这两方面的完成度都相当高。

2. 源码结构静态工程评测:模块划分与关键实现剖析

2.1 仓库目录结构与职责边界

把源码拉下来之后,第一件事就是看目录结构。SCALE-Sim 的代码组织非常规范,核心模块集中在 src 目录下。整个仓库的主要结构如下(基于 v1.x 版本,master 分支):

  • src/:整个仿真器的核心代码
    • config.py:配置文件解析,负责读入硬件参数、网络参数、数据流策略等
    • modules.py:核心仿真逻辑,包括 ProcessUnit、PEMAC、SystolicArray 等关键类
    • simulator.py:仿真主入口,负责实例化硬件模型、加载网络、逐层跑仿真
    • stats.py:统计数据的收集与保存
    • utils.py:一些辅助工具函数
  • configs/:存放硬件配置模板和网络配置
    • 例如conv_config.pyvgg_config.py
  • models/:存放预定义的 CNN 模型描述,JSON 格式
    • models/vgg16.jsonmodels/resnet50.json
  • scripts/:部分辅助脚本,比如用来跑批量实验的脚本
  • outputs/:仿真结果输出目录(默认生成)

这种划分有一个好处:硬件参数、网络模型、仿真逻辑完全解耦,你在不改代码的情况下只需要改 JSON 和 Python 配置就能完成一组新的实验。对于做架构探索的工程师来说,这一点非常重要,因为绝大多数性能评估实验本质上是参数扫描,而不是改逻辑。

2.2 config.py:所有参数的汇聚点

config.py 是整个仿真的参数中枢,里面有几个核心类或字典结构需要重点关注(不同版本略有差异,我会在后面版本边界部分详细说明)。首先是硬件配置,它定义了脉动阵列的行数和列数、每个 PE 内 MAC 的数量、片上 SRAM 的大小、DRAM 带宽等;其次是网络配置,它指向一个 JSON 格式的 CNN 模型文件,这个文件按照层的顺序描述了网络的每一层(卷积层、池化层、全连接层等)的输入输出维度和卷积核大小;最后是数据流配置,通过一个字符串或者枚举值来指定当前仿真使用 WS、OS 还是 IS 策略。

我实际测试时发现,这个文件的编码自由度很高,但也因此埋了一些坑。比如单位不统一的问题,SRAM 大小有的地方用 bit 表示,有的地方用 byte 表示,初读源码时如果不仔细,很容易在后续分析带宽时搞混。我在评测脚本里做了一层封装,把单位统一转换成 byte 后再进行计算。

2.3 modules.py 的核心逻辑:SystolicArray 与 ProcessUnit

modules.py 是整个项目工程含量最高的部分。我花时间最多的就是 SystolicArray 类以及它的实例化过程。总体来看,SCALE-Sim 的仿真流程是:将输入特征图切分成一个个 tile,然后逐个 tile 送入脉动阵列进行运算,在周期级别上模拟每个 PE 中的乘加操作。

SystolicArray 类内部管理着一个二维 PE 网格的实例列表,每个 PE 对应一个 ProcessUnit 对象。ProcessUnit 内部又包含 PEMAC 模块,这才是真正执行乘加运算的地方。每个 PEMAC 在一个时钟周期内能完成一次 MAC 操作(乘法加累加),这也是仿真器的时间基准。在仿真过程中,仿真器会按层读取网络的参数,对于每一层,计算出该层输入特征图块在阵列上的调度逻辑,最终统计出总共消耗的 cycle 数。

我特别注意到一个实现细节:SCALE-Sim 在处理多通道卷积时,会把通道维拆成多个步骤来完成,因为阵列大小是有限的,无法一次性把所有通道都装载进来。这个拆分的粒度取决于配置里的阵列的行列数,而拆分的过程直接影响了访存流量的大小。源码中这一段的注释不多,但是逻辑相对清晰,只要跟着 num_ops、num_rows、num_cols 这些变量走一遍就能理解。

2.4 统计模块的指标含义

stats.py 收集的指标中,最核心的是 utilization(利用率),它等于实际 MAC 操作数除以可用的 MAC 操作数,即总 cycle 数乘以阵列中 PE 的数量。利用率是衡量脉动阵列架构效率的最直观指标,很多论文里的对比图就是画这个利用率曲线。除了利用率,还有每层的 cycle 数、从 DRAM 读取的字节数、写回 DRAM 的字节数、片上 SRAM 的访问量等指标。

实际阅读源码时我发现,这些统计指标的计算逻辑并不复杂,它们都是在模拟过程中不断累加计数得到的结果。但因为模块之间耦合比较密,如果你只改了 config.py 中的硬件参数而忘了同步修改 stats.py 中对应的 buffer 大小配置,就可能导致输出的带宽数据不合理。这也是我建议做源码评测时一定要先跑通一个最简单的示例再修改参数的原因。

3. 实操部署:从零到一在 ARM 平台跑通 SCALE-Sim

3.1 环境准备与依赖安装

SCALE-Sim 是纯 Python 项目,所以部署的核心在于 Python 环境以及依赖库的安装。官方要求 Python 3.6 以上,依赖库主要是 numpy 和 matplotlib(部分脚本可能还需要 keras 来加载模型,但核心仿真不依赖)。我在 ARM 平台的测试环境是一台 ARMv8 架构的 Linux 服务器(飞腾 CPU),操作系统是 Ubuntu 20.04,Python 版本是 3.8。整个安装过程很顺利,没有遇到任何编译问题,这要归功于 SCALE-Sim 的纯 Python 特性。

# 建议先创建虚拟环境 python3 -m venv scalesim_env source scalesim_env/bin/activate # 安装 numpy 和 matplotlib pip install numpy matplotlib # 从 GitHub 克隆仓库 git clone https://github.com/ARM-software/SCALE-Sim.git cd SCALE-Sim

在 ARM 设备上安装 numpy 时有一个小坑:如果设备是 32 位 ARM(armv7),直接用 pip 安装预编译的 numpy wheel 可能会出现版本不匹配的问题,建议指定版本安装 numpy==1.19.5 或者在虚拟环境中用源码编译安装。如果是 ARMv8 的 64 位平台,基本不会遇到这些问题。

3.2 运行第一个仿真示例

SCALE-Sim 的启动方式分两种,一种是命令行方式,一种是 Python 脚本方式。命令行方式适合快速跑通,Python 脚本方式更适合做批量参数扫描。

命令行方式运行起来非常直接,需要指定两个配置文件路径:

python src/simulator.py --config configs/conv_config.py --model models/vgg16.json

这里的configs/conv_config.py是一个硬件配置模板,里面写了阵列大小、数据流、片上存储大小等参数,而models/vgg16.json是网络模型描述。运行完会在outputs/目录下生成一个 CSV 文件,里面按层记录着每层消耗的 cycle 数、MAC 利用率和 DRAM 访问字节数。

提示:在首次运行时建议先把configs/conv_config.py里的print_stats选项打开,可以看到逐层的详细输出。如果直接看 CSV,刚开始可能对列名比较陌生。

我用 Python 脚本方式跑了一次批量实验,对比同一网络在不同阵列尺寸下的利用率差异。核心脚本只有不到二十行:

from src.simulator import SCALESim # 示例:不同阵列尺寸下的性能对比 for dim in [32, 64, 128]: config = { 'array_height': dim, 'array_width': dim, 'dataflow': 'OS', 'ifmap_sram_size': 0, 'filter_sram_size': 0, 'psum_sram_size': 0, 'dram_bandwidth': 64 } sim = SCALESim(config=config, model='models/vgg16.json') sim.run_sim() stats = sim.get_stats() print(f"Array {dim}x{dim}: total cycle = {stats['total_cycles']}")

这个跑法非常适用于架构参数扫描,几分钟之内就能生成一组对比数据。我在实际项目中使用这个方法评估了十几种不同的阵列配置组合,极大加速了前期的架构选型过程。

3.3 在 ARM 平台跑的实际性能表现

很多人关心纯 Python 仿真器在 ARM 平台上会不会慢得没法用。我在飞腾 FT-2000/4 处理器(4 核,主频 2.6GHz)上测试了 VGG16 模型,默认阵列尺寸 32x32,OS 数据流,跑完整个模型所有层的仿真大约需要 40 秒左右。这个速度对于架构探索来说完全可接受。如果换成树莓派 4B(Cortex-A72 四核),时间大概会翻倍,但仍然在可接受范围内。

需要注意的是,SCALE-Sim 的单次仿真任务是单线程执行的,虽然脚本层面没有做多进程处理,但你可以通过并行启动多个仿真进程来利用多核性能,我通常会配合xargs -P或者 Python 的 multiprocessing 跑批量任务。另外在 ARM 平台上要注意内存限制,如果阵列尺寸配得比较大,同时模型层数又多,内存占用可能会超过 2GB,建议在跑大量实验前用free -m看一下剩余内存。

4. 版本边界与常见工程问题排查

4.1 SCALE-Sim 各版本之间的关键差异

版本边界是这次源码静态评测里最有价值的部分。GCALE-Sim 从 v1 到 v2 经历了较大的重构,很多网上搜到的教程和博客基于 v1,而官方仓库默认分支已经变成了 v2,直接照着网上教程跑很容易报错。我把两个版本的边界差异总结如下:

差异维度v1(master 旧版)v2(最新版)
配置格式使用config.py,全局变量较多,用--config指定使用 YAML 或 dict 配置,更结构化
模型输入直接读取 Keras 风格的 JSON支持更通用的 ONNX(通过转换脚本)
数据流支持WS、OS、IS 均支持在 v2 中做了更细粒度的控制
输出统计CSV 输出,字段较少统计信息更丰富,支持分层详细输出
依赖numpy、matplotlibnumpy、matplotlib、yaml
API 接口SCALEsim类实例化较简单构造函数参数更多,需要传入显式配置

版本差异带来的最大工程问题,是很多论文的复现脚本基于 v1 编写,如果你直接用 v2 的代码跑,会发现很多 API 已经变了。比如 v1 中通过configs/conv_config.py传入配置,而 v2 支持直接传入 Python dict,虽然功能更强了,但对老用户来说增加了迁移成本。

4.2 常见报错与排查方法

  • ModuleNotFoundError: No module named 'src.tools':常见于直接从 GitHub clone 后马上运行的情况。这是因为仓库中部分辅助模块依赖 setuptools,需要先安装项目依赖包pip install -e .或者手动添加 sys.path。
  • Keras JSON 模型读取失败:如果你用 v1 版本并尝试加载自定义 JSON 模型,很可能因为 JSON 格式中缺少某个字段导致解析失败。建议先检查模型文件里每一层是否都包含nFiltersnChannels等字段。
  • 仿真结果全为 0:遇到这种问题首先检查阵列尺寸是否比卷积核还小,如果配置阵列 4x4 但卷积核是 7x7,仿真器无法正常调度。SCALE-Sim 要求阵列尺寸必须大于等于卷积核尺寸。
  • DRAM 带宽远高于理论值:极有可能是dram_bandwidth的单位搞错了。v1 中单位是字节/周期,v2 中改成了 bit/周期,没注意单位转换的话,带宽数值会出现 8 倍的偏差。我在写批处理脚本时专门封装了一个单位换算的函数来规避这个问题。

4.3 值得注意的几个工程坑

SCALE-Sim 的源码本身就是很好的 Python 工程教材,但有一些小问题在你阅读或者做二次开发时需要留意。

第一,全局变量和局部变量的管理比较随意,它的config.py在读入配置的时候直接把一堆全局变量塞进 namesapce,导致后续模块之间通过from config import *访问,这种写法在快速原型阶段很方便,但如果你要把它集成到更大的框架里,用这块代码之前最好先做一层封装,把参数显式传给仿真器。

第二,outputs/目录默认会覆盖写入,多次运行同一个配置时,如果不在脚本里改名输出文件,上一次的结果会被直接覆盖。我在批量扫描时踩过这个坑,丢掉了一批数据,后来在脚本里强制要求每次运行都生成时间戳目录。

第三,执行效率优化空间很大。对于大阵列(比如 128x128)跑大型网络,Python 层级的嵌套循环会成为瓶颈。如果想要进一步提速,可以考虑把核心调度循环用 Cython 或者 Numba 重写。不过就我们常见的评估场景来说,默认实现已经够用了。

4.4 关于扩展方向的一点个人经验

如果把 SCALE-Sim 只是当作一个黑盒工具来用,那它的价值也就止步于跑跑配置、看看结论。但如果你愿意花时间深入源码,它完全可以变成你自己的架构探索平台。我在使用过程中做了几个扩展,给刚接触的朋友做个参考:一是把仿真输出接入了自研的可视化前端,可以动态展示不同层在不同数据流下 MAC 利用率的热力图;二是增加了对稀疏化权重跳过零值的建模(目前官方版本不支持,你需要自己改 modules.py 中的 MAC 计数循环);三是把模型解析部分换成了 ONNX 解析器,这样就能直接仿真一些新出的开源模型结构。

这三个扩展方向的代码改动量都不大,核心逻辑仍然在SystolicArray类的调度循环里,这也恰好说明了 SCALE-Sim 在架构设计上的可扩展性确实不错。如果你本身就对脉动阵列和 AI 加速器感兴趣,从这份源码入手,可能比直接读论文的收获要大得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询