ARM 架构 JuiceFS 性能优化:基于 MLPerf 的实践与调优

随着国产芯片与 ARM 生态的快速发展,如何在 ARM 平台上构建高性能存储基础设施成为技术焦点。Linaro 是一个专注于 Arm 生态和开源软件的国际化技术组织,联合产业链上下游厂商解决共性问题,并协助企业客户在开源基础上完成产品化落地。Linaro 团队在 MLPerf Storage 测试中,对 JuiceFS 社区版(元数据采用 Redis)进行了系统压测,覆盖多种典型机器学习训练负载。

测试结果显示,系统性能在很大程度上受内存带宽和元数据访问效率影响,JuiceFS 的吞吐能力直接决定 GPU 利用率及训练效率。通过 UNet3D、ResNet-50 和 CosmoFlow 等负载的测试,分析发现:单机场景下,GPU 利用率主要受内存拷贝延迟限制;在双机或多机场景下,元数据访问和节点间同步成为主要瓶颈。文章同时提供了针对这些瓶颈的调优思路与实践结果。

总体来看,大规模 AI 训练性能调优是一个系统工程,需要从存储系统、内存带宽、CPU 调度、缓存策略等多方面协同优化,才能在 ARM 平台上实现高效的深度学习训练数据供给。

01 Arm64 与 x86_64 架构差异与并发特性概述

相比 x86,Arm 的应用范围不断扩大,已从移动端延伸至 IoT、可穿戴设备、PC、汽车和服务器,其高能效比(performance per watt)是广泛采用的关键原因。

从架构设计上看,Arm 属于 RISC(Reduced Instruction Set Computer,精简指令集计算机),x86 属于 CISC(Complex Instruction Set Computer,复杂指令集计算机)。这种设计差异也影响了处理器的执行方式。Arm64 的指令长度固定为 4 字节,而 x86 指令长度可变,大约在 1--15 字节 之间,因此 x86 往往需要更复杂的译码器。相比之下,Arm 的指令更简洁,也更依赖编译器和代码生成阶段对指令的有效组织,所以需要更长的编译时间。

从工程师可感知的角度来看,还有一些架构差异会直接影响程序行为。很多在 x86 上看起来符合直觉的代码,在 Arm 上未必如此,后面要讲到的几个容易踩坑的点,基本都与这些底层差异有关

其中一个典型问题是原子操作对地址对齐有要求。无论是 LL/SC(Load-Link/Store-Conditional),还是 LSE(Large System Extensions),在执行原子加减等读改写操作时,通常都要求访问地址满足对齐条件。较新的 LSE2 对这一限制有所放宽,开始支持 16-byte window 内的非对齐访问。数据对齐对于 x86 来说不是必须的,但保持良好对齐有助于提升性能。参考文档: Arm Architecture Reference Manual for A-profile architecture

另一个需要重点关注的特点是,Arm 采用的是弱内存序模型(weakly ordered / relaxed memory model),差别体现在对内存访问顺序的约束强弱不同。在多线程场景下,同样的读写操作,在 x86 上通常更容易表现为接近程序书写顺序,而在 Arm 上则允许更多重排序,因此其他线程观察到的读写顺序可能与源码顺序不一致。在 Arm 上出现的异常需要特别考虑内存序的影响。更多细节可参考 Arm 白皮书:Synchronization Overview and Case Study on Arm Architecture

02 JuiceFS 与 MLPerf 概述

JuiceFS 是一款开源高性能分布式文件系统,基于对象存储构建,在充分利用对象存储低成本优势的同时,提供接近传统文件系统的使用体验。它支持 POSIX、HDFS SDK、Python SDK 以及 S3 兼容接口,并能适配不同类型的应用和数据处理框架;同时支持云原生扩展、数据安全与压缩,可广泛应用于 AI 训练、推理、大数据处理等场景。

为评估 JuiceFS 在 AI 训练等高负载场景下的数据供给能力,可采用 MLPerf Storage 基准测试。该测试由 MLCommons 开发,重点衡量存储系统能否持续高效向计算侧提供数据。

2.0 版本将测试分为训练负载和检查点负载两类,其中训练负载包括 3D U-Net、ResNet-50 和 CosmoFlow,三者在样本大小和访问特征上存在显著差异,并设置了最低 GPU 利用率要求:3D U-Net 与 ResNet-50 为 90%,CosmoFlow 为 70%。

在测试流程中,数据首先从存储系统读入主机内存,再进入计算阶段。训练耗时由模拟方式复现,以模拟真实训练场景的数据流,从而无需实际部署 GPU,降低实验门槛并提升操作便利性。

03 MLPerf Storage v2.0 测试原理与调优

在介绍具体的模型测试结果之前,需要先了解分布式训练的数据访问原理,有助于读者明白 GPU 利用率、存储吞吐和性能瓶颈的成因,从而更好地理解后续的测试结果和调优策略。

分布式机器学习通常采用数据并行方式,即多个并行进程共享同一数据集,每个进程分别负责读取和处理对应的训练批次。

MLPerf Storage 的训练测试也遵循这一思路,每个训练进程按批次从存储系统读取数据,并通过模拟计算来评估存储系统的持续供数能力。

为了理解测试中性能表现的来源,还需要理解 JuiceFS 客户端的数据处理链路。使用 JuiceFS 进行测试时,如图所示,其执行流程大致可分为三部分。

  • 左侧:应用侧 I/O 线程,例如 fio 或 MLPerf Storage 的 DataLoader 线程,负责发起 read/write 请求并等待请求完成。
  • 中间:FUSE 守护进程中的请求处理主 goroutine,负责接收并处理来自内核态的 FUSE 请求,将文件数据放入内存缓冲区和缓存,并触发后端元数据与对象存储访问。
  • 右侧:Meta client 和 ObjectStore client 的异步 goroutine,负责与后端 MetaDB 和 ObjectStore 集群进行数据与元数据交互。

从性能分析的角度看,这条链路需要重点关注两类问题。第一类是数据拷贝,对应图中的 2.1、3、4、5、6 等步骤,这些位置都会带来额外的内存复制开销,因此往往是分析延迟和 CPU 开销时的重点。

第二类是同步与异步边界。从图中可以看出,1、2、3、4、5、6 这些步骤总体属于同步路径,也就是请求发起后,需要等待当前阶段完成才能继续向下推进;而 7 属于异步路径,由后台 goroutine 负责与后端存储交互。

测试 1:Unet 3d

在这个测试中,样本为 146 MiB 的图像文件,我们主要关注大块数据的读取性能。测试结果显示:

  • 单机环境下最高可稳定运行 5 块 GPU,GPU 利用率约为 50%,
  • 而双机场景可支持 10 块 GPU,GPU 利用率同样约为 50%。

为了提升数据读取效率,对训练参数进行了优化:将 reader 并发线程数从 4 调整为 16,以加快数据生成速度,并将数据读取方式改为 direct I/O,以减少缓冲区和内存拷贝开销。

业务指标显示,当单机挂载 6 块 GPU 时,GPU 利用率仅为 83%,对应带宽约为 15.1 GB/s,未达到预期的高利用率目标 。进一步使用 FIO 对存储侧进行测试后发现,其带宽同样约为 15.1 GB/s,说明此时系统瓶颈已经落在 JuiceFS 客户端带宽 上,而不是 GPU 计算侧本身

优化分析 1:绑定 CPU

为了深入分析客户端带宽受限的原因,我们对进程进行了 CPU 绑定,将其固定在 CPU1(NUMA 2、3 节点)运行。通过工具观察,48 个 CPU 核心几乎全部占满,进一步分析 top-down、memory 和 miss 指标发现,系统表现出明显 Memory Bound,主要耗时集中在内存拷贝上。这说明,在 CPU 绑定场景下,JuiceFS 性能瓶颈主要来自 CPU 处理能力和跨 NUMA 节点内存拷贝带来的额外延迟。

优化分析 2: 不进行 CPU 绑定

为了理解系统在更通用条件下的带宽限制,我们进一步观察了不绑定 CPU 的情况。通过观察可以看到,CPU 并未被完全用满,但 devkit tuner numafast 指标显示,系统中的 remote 内存访问比例高达约 80%。这意味着,大量内存访问已经跨越本地 NUMA 节点,甚至可能跨越 CPU socket,从而引入了显著的带宽损失和访问时延。

从硬件带宽特性看,跨片内存访问本身就存在明显限制。例如,在 Arm 平台上,跨 socket 的理论物理带宽约为 60 GB/s;进一步通过实测,跨片 copy 带宽在 Arm1 上约为 48 GB/s,而在两组 x86 平台上分别约为 37 GB/s 和 28 GB/s。

这说明,在不绑定 CPU 的情况下,虽然计算核表面上没有被完全耗尽,但大量跨节点、跨 socket 的远端内存访问已经成为新的主要开销来源。因此,可以推测,此时 JuiceFS 带宽无法继续提升,很可能并不是单纯受限于 CPU 算力,而是受限于跨片内存访问的带宽与时延。换言之,系统瓶颈已经从"本地 CPU 忙不过来"转移为"远端内存访问代价过高"

综合来看,在两种场景下,JuiceFS 带宽无法提升的原因并不相同:

  • 绑定 CPU 时,主要受限于 CPU 资源消耗以及大量内存拷贝带来的访问开销;
  • 不绑定 CPU 时,主要受限于高比例 非本地内存访问,尤其可能是跨 socket 访问带来的带宽和时延损失。

测试 2:Resnet50

ResNet-50 测试的单个样本较小,单个样本大小约为 150 KiB,每个 batch 包含 400 个样本,每个 batch 的总数据量约 58.5 MiB。本次 I/O 测试关注 GPU 高并发下的数据加载效率及训练吞吐。测试显示系统可在大规模 GPU 下维持较高利用率:

  • 单机:50 块 GPU,GPU 利用率 95%,带宽约 9.2 GB/s
  • 双机:96 块 GPU,GPU 利用率 90%,带宽约 16.9 GB/s

在测试过程中,我们将关键参数 reader.read_threads 从 8 调整为 1,对于该模型(中等大小的图像模型),单线程即可满足数据供给需求。

优化分析 1: 单机性能瓶颈与内存带宽影响

在单机配置 55 块 GPU 时,GPU 利用率下降至 86%,带宽仍为 9.2 GB/s,表明系统瓶颈已转移至 JuiceFS 客户端带宽。

进一步分析发现,ResNet-50 测试采用 Buffer I/O 模式,除了读取数据外,处理数据集时的 内存拷贝会消耗一部分内存带宽。

系统内存拷贝带宽受内存通道数、内存频率及 CPU 频率影响。通过对多台配置不同的机器进行 stream 测试,得到的单机顺序读带宽与系统内存带宽测得的可比带宽一致,表明读数据吞吐能力在很大程度上取决于系统内存带宽。对于需要高吞吐、高 GPU 利用率的训练任务,建议优先选择内存带宽较高的机型,可显著提升数据供给能力和训练效率

单 CPU 内存拷贝带宽数据 JuiceFS 单机部署读带宽
Arm3 Arm3: 171 GB/s 25.3 GiB/s
Arm2 114 GB/s 21.6 GiB/s
Arm1 106 GB/s 18.3 GiB/s
x862 90 GB/s 17.9 GiB/s
x861 82 GB/s 16.6 GiB/s

优化分析 2:双机扩展瓶颈与分布式限制

在多节点部署中,除了单机性能限制外,跨节点内存访问、网络传输和元数据延迟会成为新的瓶颈,因此在单机分析之后进行双机测试,有助于识别这些分布式约束并指导系统优化。

在双机场景下,理论上可以支持 100 块 GPU,但实际测试中只能达到 96 块 GPU。通过分析发现,每个操作的读取延迟有所增加。尽管文件数据已缓存在本地盘上,元数据访问延迟仍然成为主要限制因素。

为解决这一问题,对系统进行了多方面优化:

  1. 将 CPU 核心分组,保证训练线程与 I/O 线程在同一 NUMA 节点上运行。
  2. 将纯数据处理和元数据访问分别分配到不同 CPU 核心和存储路径上。
  3. 调整 Redis 缓存和本地缓存策略,减少高并发访问元数据时的延迟。

经过上述调优后,双机场景能够稳定支撑 100 块 GPU 运行,GPU 利用率达到预期水平。

测试 3:cosmoflow

与之前的模型相比,这个模型的单样本数据量更小,这也意味着对 I/O 和元数据访问的要求更高。在单机和双机场景下,CosmoFlow 测试显示:

  • 单机:最高稳定 10 块 GPU(偶尔可撑到 12 块 GPU),GPU 利用率约 75%,带宽约 5.6 GB/s
  • 关键参数调整:将 reader.read_threads 从 4 调整为 1,每次读取 batch 数据量为 2 MiB,单线程即可满足数据供给需求。

优化分析 1:单机瓶颈:内存拷贝限制 GPU 利用率

当尝试增加 GPU 数量超过 10 块时,发现 GPU 利用率下降。分析日志和性能数据后发现:

  • 数据读取时间增加,而元数据访问延迟变化不大。
  • 文件数据已缓存在本地盘上,磁盘队列未满,延迟也不高,因此瓶颈不在存储设备。
  • 使用性能分析工具观察发现,关键瓶颈操作主要集中在 内存拷贝(memcopy),数据读取流程中多次拷贝操作的延迟累积,导致整体读取时间增加。

由此推测,当系统使用更多内存带宽时,内存拷贝延迟成为限制读取性能和 GPU 利用率的主要因素。

优化分析 2:双机瓶颈:分布式同步与元数据延迟

在双机场景下,尝试 20 块 GPU 时,第一轮测试 GPU 利用率明显偏低。进一步分析发现:

  • 一台机器已开始训练,而另一台机器仍在进行 Dataset 预处理,包括读取文件列表和分片操作。
  • 由于 CosmoFlow 数据量较大,高索引文件的读取耗时较长,导致两台机器未能同步开始训练,第一轮 GPU 利用率下降。

为解决该问题,在代码中加入同步机制,确保所有节点在开始训练前完成 Dataset 预处理。经过该调整后,双机测试能够稳定支撑 20 块 GPU,GPU 利用率达到预期水平。

04 总结

首先,MLPerf Storage 通过不同样本、文件和 batch 大小的组合,考察文件系统的各项能力,包括大中小块顺序读能力、文件并发性能、总读带宽、元数据访问时延、文件读取时延以及文件操作的稳定性。在只读文件场景下,充分利用高速近端缓存(包括原数据和元数据缓存)可以显著提升读取性能。需要注意,文件越小,对 IOPS 和延迟的要求越高。

其次,我们发现系统的内存和带宽对性能影响显著。在 Memory copy 密集型的应用中,内存拷贝不仅消耗内存带宽,同时也占用 CPU,表面上会出现"CPU 忙"的假象,实际上 CPU 大部分时间是在等待数据。测试结果显示,系统内存带宽对 JuiceFS 的吞吐能力有决定性影响,这也为选择服务器提供了参考标准:内存带宽越高的系统,其存储吞吐性能也越好。

第三,Go 运行时对 NUMA 的感知有限,对于大规模 CPU 核心运行场景,性能可能不如小规模核心运行。对于多 NUMA 系统,应尽量避免跨 NUMA,尤其是跨 CPU socket 的访问,因为跨 socket 内存带宽通常较低(约几十 GB/s),会增加延迟并影响整体性能。因此,实际部署时只需要分配足够的 CPU 核心即可,无需过度使用全部核心,以避免额外的内存访问延迟。

第四,在系统层面还有一些潜在优化点。例如,对于 Memory copy 密集的操作,部分 Arm 新系统提供了针对内存访问的指令优化,我们与 Arm 社区合作,将配置提升推送到社区中,新系统可以显著提升内存拷贝效率,在部分场景中带宽提升可达数十个百分点。

此外,对于涉及大量内核与用户态交互的操作,例如文件读写和元数据处理,可以通过优化用户态与内核态的交互,减少不必要的调用次数,从而降低延迟。实践中也发现,将文件处理尽量集中在同一生产节点内,避免跨 NUMA 或跨 socket 的访问,可以进一步提升性能和稳定性。

最后,系统配置优化也体现在缓存策略上。例如,在单机高负载场景下,通过调整 JuiceFS 的内存缓存策略,减少无效内存带宽占用,可以有效提高 GPU 利用率和存储吞吐。整体来看,MLPerf Storage Benchmark 是一个系统工程,需要文件系统、内存带宽、CPU 调度和缓存策略等多方面配合,才能达到最优性能。

我们希望本文中的一些实践经验,能为正在面临类似问题的开发者提供参考,如果有其他疑问欢迎加入 JuiceFS 社区与大家共同交流。

相关推荐
万少6 小时前
Vibe Coding不停歇,移动端 TRAE SOLO 让你用手机也能编程啦
前端·javascript·后端
Rust研习社6 小时前
为什么 Rust 没有空指针?
开发语言·后端·rust
皮皮林5516 小时前
全网最全的 Jenkins + Maven + Git 自动化部署指南!
后端
舒一笑7 小时前
用几十行代码搞定 Chat 接口透明转发:跨环境轻量级网关实战
后端·程序员·架构
铁皮饭盒8 小时前
成为AI全栈 - 第3课:路由 RESTful Elysia 状态码 设计规范
前端·后端·全栈
顾昂_8 小时前
Web 性能优化完全指南
前端·面试·性能优化
我叫黑大帅8 小时前
如何通过 Python 实现招聘平台自动投递
后端·python·面试
狼爷8 小时前
短视频播放量(Views)计数系统实现方案:高并发、不丢数的工业级实践
后端·架构
苍何9 小时前
我用 Tabbit 浏览器搭了一套内容创作全自动流水线,太香了!
后端
苍何10 小时前
全网首测,TRAE SOLO 的 AI 麦克风!
后端