技术分析表明在 AI 推理任务中,传统 CPU 性能仍优于专用 NPU 芯片,对硬件选型有参考价值。
TL;DR——在对 Windows AI PC 进行基准测试时,我们测得的性能只有 Qualcomm 宣称的 NPU 45 Teraops/s 的 1.3%。
安装 Python、Cmake、Visual Studio、Pip Packages
基准测试:运行、理解输出、基准测试的衡量内容、可能的混淆因素、计算受限、功耗设置、模型拓扑、配置错误、Onnx Framework
Microsoft 现在推出了搭载 Qualcomm Arm SoC、运行 Windows 的 Surface 平板电脑。这些设备凭借比其他系统更快、更高效地运行机器学习模型的能力,被宣传为 AI PC。我们很喜欢 Qualcomm 的硬件,尤其是它的 NPU,因此投入了大量时间和资源,将我们的第三方应用移植到这个平台。
遗憾的是,目前几乎没有代码示例或基准测试能够向外部开发者展示如何实现高性能。因此,我们整理了一个小型独立项目,用来展示我们观察到的性能。结果远低于我们的预期,所以我们将这个基准测试公开出来,希望获得一些如何降低延迟的建议。我希望未来能通过应用、框架或驱动层面的软件改进来提升这些结果,因为我曾看到相同的底层硬件在 Android 等其他平台上表现得非常出色。
我们使用 Python 运行测试脚本,而在 Windows 上安装这门语言有多种方式。截至 2024 年 10 月 2 日,Microsoft Store 提供的 Python 尚不支持 Arm 架构,因此无法运行访问 Qualcomm NPU 所需的 packages。你应当改用 Python dot org 提供的官方安装程序。本文报告的测试结果使用的是 3.11.9 版本。
我们还需要 cmake 构建工具来编译 Onnx,因为目前还没有适用于 Windows on Arm 的预编译 packages。为此,我在 Powershell 中运行了以下命令:
winget install cmake
构建过程还需要使用 Visual Studio 提供编译器。请从 visualstudio.microsoft.com/downloads/ 下载 Visual Studio Community Edition,注意不是 Visual Studio Code!
安装过程中,系统会提示你从多个选项中选择 Workload:勾选 Desktop C++ Development,然后点击 install。
在当前文件夹中运行以下命令,即可安装全部必需的 Python packages:
py -m pip install -r requirements.txt
其中包括几个自定义 packages。第一个是我的 Onnx 分支,它将使用官方 py launcher 进行编译的修复反向移植到了 Onnx 1.16,因为 Qualcomm Onnx Runtime 无法与较新的 Onnx 版本配合使用,会出现 Unsupported model IR version error。
我还使用了 Qualcomm Onnx Runtime package 的 nightly build。如果你想安装更新的版本,可以参考这里的列表。
要执行基准测试,请运行:
py benchmark_matmul.py
Onnx runtime 最初会生成大量日志,其中包括:
Error in cpuinfo: Unknown chip model name 'Snapdragon(R) X 12-core X1E80100 @ 3.40 GHz'.
Please add new Windows on Arm SoC/chip support to arm/windows/init.c!
unknown Qualcomm CPU part 0x1 ignored
Starting stage: Finalizing Graph Sequence
Completed stage: Finalizing Graph Sequence (115919 us)
Starting stage: Completion
Completed stage: Completion (1025 us)
在这些信息全部输出之后,你应该会在末尾看到实际的基准测试结果,大致如下:
************ Benchmark Results ************
NPU quantized compute, float I/O accuracy difference is 0.0100
NPU quantized compute and I/O accuracy difference is 0.0060
CPU took 8.42ms, 821,141,860,688 ops per second
NPU (quantized compute, float I/O) took 30.63ms, 225,667,671,183 ops per second
NPU (quantized compute and I/O) took 12.05ms, 573,475,650,364 ops per second
前两行确认了 CPU 与 NPU 的运算结果在数值上相符。最后三行则展示了运行一个简单模型时,三种方案各自的延迟。这里的延迟是从开始到结束执行整个模型所需的实际时间;每秒操作数则根据该延迟计算得出,用来表示等效计算吞吐量。
在这个例子中,CPU 能够达到每秒 8210 亿次操作,即 821 Gigaops;第一种 NPU 方案达到 225 Gigaops,第二种则达到 573 Gigaops。
这个基准测试旨在模拟我们所依赖的一些实际模型。它会执行 6 次大型矩阵乘法,类似于 OpenAI Whisper 等 transformer 模型中最耗时的层。矩阵形状为 (6, 1500, 256) X (6, 256, 1500),生成形状为 (6, 1500, 1500) 的结果。我们运行的模型由一个 MatMul 节点组成,包含两个输入和一个输出。
这些模型使用 Onnx model framework 动态创建,然后传入 Onnx runtime。作为对照的模型是纯 float 版本,完全在 CPU 上运行。
NPU 基本上需要使用量化模型才能高效运行,不过它也提供了有限的 float16 支持。我们采用的第一种量化方案使用了官方的 ORT quantize_static() 方法。为了方便,该方法会将输入和输出 tensor 保留为 32-bit float,并在 graph 的开头和结尾执行运行时转换,使其余计算都以 eight-bit 形式进行。
遗憾的是,我们发现 NPU 上实现的这些转换操作极其缓慢,事实上比主要的矩阵乘法还要慢。你可以在该 repository 的 npu_quant_profile.csv 文件中看到结果,其中转换操作占用了超过 75% 的时间。
为了解决这个问题,我们通过编程方式构建了一个等价的 model graph,并使用 eight-bit 输入和输出。这就是结果中提到的第二种“quantized compute and I/O”方案。它通常比 float I/O 版本快约三倍,而且 profiling 显示,大部分时间都花在了矩阵乘法上——这正是我们所期望的结果。
衡量性能涉及许多变量。以下是我们采用的一些假设:
现代 transformer 模型以大型矩阵乘法为基础,这一点不同于较早的 convolutional 模型。一个潜在问题是,如果 layer 开始接近矩阵与向量相乘的形式,accelerator 可能会受到内存带宽限制,因为这种运算无法大量复用 weights,性能瓶颈会转移到从 DRAM 获取数值上。我们尝试通过让两个输入矩阵更加接近方形来避免这一问题,从而使 tiling 和数据复用成为可能。
tiny Whisper 模型中的原始矩阵,其 k dimension 只有 64。为了防止这个值过小,我们在本基准测试中将它提高到了 256,尽可能为 SIMD 优化留出空间。
Windows 提供了许多与能源使用相关的配置选项,因此我们尽量确保所有设置都处于 "Best Performance",并在平板电脑连接市电的情况下运行基准测试。Qualcomm Onnx Runtime 还提供了一个 session option——htp_performance_mode。我们将它设为 sustained_high_performance,因为在实验中,这个设置似乎能带来最低的整体延迟。
我们希望创建一个能够反映现代 AI 模型的 operation graph,同时又足够简单,便于解读。我们原本可以添加多个 layer、使用 convolution 或 static weights,但最终选择了只有一次矩阵乘法且使用 dynamic inputs 的方案,因为这更能反映 LLM 和其他现代模型广泛采用的 transformer 架构。
我们构建和运行模型的方式,有可能导致它们无法走 drivers 或 accelerator implementation 中的 fast path。例如,我们使用的是 unsigned eight-bit quantization,并在 graph 中包含 qdq elements。我们已经尽力遵循文档中的最佳实践,但仍然欢迎任何改进性能的方法,尤其是这些方法也会提升我们实际应用的性能。
在 Windows 上访问 AI acceleration 有多种不同方式。我们研究过 DirectML,但它似乎只支持访问 GPU。据我们所知,OpenVino 无法在我们的 Arm 硬件上运行。直接使用 Qualcomm QNN SDK 时,我们也观察到了与本文类似的性能结果。TensorFlow Lite 不支持 Windows for Arm。根据这些调查和我们的实验,Onnx 同时获得 Microsoft 和 Qualcomm 的支持,似乎是利用 NPU 获得加速性能的最佳 framework;不过,如果其他 API 更适合这个场景,我们也很有兴趣了解。
本文展示的结果截至 2024 年 10 月 2 日有效,测试设备为 Microsoft Surface Pro 11th Edition,搭载主频为 3.40 GHz 的 Snapdragon(R) X 12-core X1E80100。最明显的一点是,即使不进行 float 转换,NPU 的结果仍然比 CPU 慢。对 accelerator 来说,这显然并不理想。不过,NPU 仍有可能在能耗或持续性能方面具备优势,从而让它值得使用。
第二个结论是,我们测得的性能为每秒 5730 亿次操作,只有宣传材料承诺的每秒 45 万亿次操作的 1.3%。
相比之下,在 Nvidia Geforce RTX 4080 Laptop GPU 上运行相同模型只需 3.2ms,等效性能为每秒 2.16 万亿次操作,吞吐量接近前者的四倍。