ARM架构JuiceFS性能深度优化:MLPerf实战调优全解析
先提炼几个关键结论:在Arm架构上运行大规模AI训练任务,存储性能调优涉及多个环节的系统级协作。最近Linaro团队针对JuiceFS社区版完成了全套MLPerf Storage基准测试,覆盖UNet3D、ResNet-50和CosmoFlow这几种典型负载,测试结果揭示了多个层面的性能瓶颈。
测试数据清晰地表明:系统吞吐能力主要受内存带宽和元数据访问效率的双重制约。JuiceFS的带宽直接决定了GPU能否跑满。单机环境下GPU利用率主要受内存拷贝延迟制约;进入双机或多机阶段后,元数据访问延迟和节点间同步成为新的瓶颈。文章后续给出了具体的调优思路和实测效果。
本质上,大规模AI训练的性能不能只盯着存储系统,必须从内存带宽、CPU调度、缓存策略等多个维度协同优化,才能在Arm平台上为深度学习训练建立扎实的数据供给基础。
01 Arm64 与 x86_64 架构差异与并发特性概述
Arm的覆盖范围正从移动端向IoT、可穿戴设备、PC、汽车和服务器快速扩展,背后核心驱动力是其出色的能效比。这块已被业界广泛认可。
底层设计上,Arm属于RISC(精简指令集计算机),x86属于CISC(复杂指令集计算机)。这一根本差异决定了处理器的执行方式。Arm64的指令长度固定为4字节,x86则为1~15字节的变长指令,因此x86需要更复杂的译码器。Arm指令虽然简洁,但更依赖编译器在代码生成阶段对指令的有效编排,编译时间自然更长。
从工程师的视角看,这些架构差异会直接影响程序行为。很多在x86上写起来顺手的代码,放到Arm上可能表现完全不同。后面提到的几个易踩坑的点,基本都源于这些底层差异。
一个典型问题是原子操作对地址对齐有严格要求。无论采用LL/SC(Load-Link/Store-Conditional)还是LSE(Large System Extensions),执行原子加减这类读改写操作时通常要求访问地址满足对齐条件。较新的LSE2对此有所放宽,开始支持16-byte窗口内的非对齐访问。数据对齐对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,这表明此时系统的瓶颈已从GPU计算侧转移至JuiceFS客户端带宽。
优化分析 1:绑定 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个样本,总数据量约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块。分析发现,每个操作的读取延迟有所增加。虽然文件数据已缓存在本地盘上,但元数据访问延迟仍是主要限制因素。
为解决此问题,我们对系统进行了多方面优化:
- 将CPU核心分组,确保训练线程和I/O线程在同一个NUMA节点上运行。
- 将纯数据处理与元数据访问分别分配到不同的CPU核心和存储路径上。
- 调整Redis缓存和本地缓存策略,减少高并发访问元数据时的延迟。
经过这些调优后,双机场景能稳定支撑100块GPU运行,GPU利用率也达到了预期水平。
测试 3:cosmoflow
与前两个模型相比,该模型的单样本数据量更小,意味着对I/O和元数据访问的要求更高。在单机和双机场景下,CosmoFlow的测试结果显示:
- 单机:最高稳定10块GPU(偶尔能撑到12块),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调度和缓存策略等多方面配合,才能达到最优性能。





