1. 背景与总体方案设计思路
1.1 为什么要在 ARM 架构上跑这套组合
先说结论:ARM 服务器在 AI 基础设施里已经不是“能不能用”的问题,而是“怎么把性能榨干”的问题。这两年我陆陆续续接过几个基于鲲鹏和飞腾平台的项目,其中一个比较典型的场景就是:训练集群换成了 ARM 架构的服务器,但上层的数据访问链路还停留在老的 x86 时代的习惯,导致 MLPerf 基准测试一跑起来,GPU 经常在等数据,算力利用率惨不忍睹。
这个项目的核心矛盾其实很简单:MLPerf 是衡量 AI 训练和推理性能的基准测试集,它考验的是整套系统的综合能力。但很多人只盯着 GPU 的算力,忽略了存储这一环。而 JuieFS 这种分布式文件系统恰恰是连接存储和后端算力的关键桥梁。JuiceFS 最大的特点是元数据和数据分离,数据下沉到对象存储,元数据交给独立的数据库(比如 Redis),客户端负责缓存和协议转换。这套架构的好处是扩展性好、成本低,但代价是性能非常依赖客户端的调优程度。
ARM 架构带来的第一个麻烦就是:很多针对 x86 指令集做的优化在 ARM 上完全失效,尤其是一些汇编级别的加速逻辑和内存屏障相关的处理。第二个麻烦是ARM 服务器的 CPU 频率策略、内存带宽、NVMe 设备驱动表现和 x86 平台差异很大,如果不针对性地调,JuiceFS 客户端的缓存命中率和元数据操作延迟都会明显变差。
1.2 整体方案选型的前期调研
项目立项后的第一周,我主要干了三件事:确认硬件规格、评估 JuiceFS 的 ARM 兼容性、确定 MLPerf 的具体测试子项。
硬件这块,当时手头有两台 ARM 服务器可以作为测试节点,CPU 是鲲鹏 920(64 核,2.6GHz),内存 256GB,系统盘是两块 NVMe SSD组 RAID 1,另配了一块 3.2TB 的 NVMe 盘作为 JuiceFS 的本地缓存盘。GPU 是四张加速卡,跑的是 MLPerf Training 里的 ResNet50 图像分类子项。这个子项对存储的要求比较典型,既有大量小文件的随机读取(训练数据),又有周期性的大文件写入(checkpoint),是一个非常全能的测试场景。
JuiceFS 这边我重点看了两点:一是官方是否提供 ARM 架构的预编译二进制包,二是源码编译时的依赖情况。JuiceFS 本身是 Go 语言写的,跨架构编译天然有优势,官方 release 里的 linux-arm64 包可以直接用,但我建议还是从源码自己编一遍,原因后面会说。
MLPerf 方面,我选了 MLPerf Training v1.1 的 ResNet50 子项作为主要基准,理由是这个子项的测试流程成熟,数据加载逻辑(基于 tfrecord 格式)在存储层压力模型很清晰:训练开始时要顺序读大量 tfrecord 文件,训练过程中每隔若干 step 要写一次 checkpoint,而且官方实现里还有一次数据预处理阶段,对小文件随机读的要求很高。这几个阶段正好能覆盖 JuiceFS 的读缓存、写缓存、元数据操作三个核心路径。
核对了这几个方面后,整个调优思路就清晰了:不做大的架构改动,而是把 JuiceFS 客户端在 ARM 平台上的适配和参数调整作为切入点,配合操作系统层面的一些设置,让 MLPerf 的训练流程不再被存储拖后腿。
2. ARM 环境下 JuiceFS 部署与关键编译优化
2.1 源码编译时需要留意的几个细节
JuiceFS 官方提供的 linux-arm64 二进制包一般人直接拿来就能用,但我强烈建议在 ARM 服务器上自己编译一遍,原因有三个:一是可以加上-tags参数启用特殊的缓存后端驱动;二是可以在编译时选择是否启用tcbt(一个元数据缓存扩展,这个参数对高并发元数据操作有明显帮助);三是自编译的版本可以验证整套工具链在 ARM 平台上的兼容性,方便后续二次开发。
我当时的编译命令是这样的:
git clone https://github.com/juicedata/juicefs.git cd juicefs # 需要 Go 1.20+,ARM 平台建议用官方工具链,避免交叉编译带来的运行时不确定 go build -tags "tcbt" -ldflags "-s -w -X github.com/juicedata/juicefs/pkg/version.Version=v1.2.0-arm"编译依赖的二进制确实没遇到什么跨平台的坑,但有一点要提醒,如果用低版本的 Go,编译出来的二进制在 ARM 平台高并发场景下可能会出现内存占用持续上升的问题。建议用 Go 1.20 或者更高版本,原因在于这类版本对 ARM64 的垃圾回收参数做了优化,配合 JuiceFS 自身的缓存回收逻辑会稳定很多。
编译完先跑一个简单的版本确认:
./juicefs version # 输出中能看到 version v1.2.0-arm,说明编译成功2.2 元数据引擎的选型与 ARM 适配
JuiceFS 的元数据引擎可以接 Redis、TiKV、MySQL 等,我这里选了 Redis,因为部署简单,性能好,尤其是在元数据操作密集的场景下延迟比较低(在 ARM 平台跑 Redis 没有架构限制)。我用的是 Redis 6.2.6,这是比较稳的版本。
关键的配置有几点:
- 关闭 Redis 的 AOF 持久化。JuiceFS 元数据引擎不需要通过 Redis 的持久化来保证数据安全,底层对象存储才是最终的数据源,Redis 挂了可以重建元数据。AOF 开启会引入 fsync 操作,明显拉高写入延迟,尤其是 ARM 平台上的磁盘驱动力度比 x86 弱一些,影响会更明显。
maxmemory设置了 16GB,策略是allkeys-lru。稳定运行下来,MLPerf 的数据集大概十几万个文件,元数据占用大约 3~4GB,16GB 留了充足的余量。- 开启
appendonly no,同时把save ""写进配置,避免快照。
Redis 本身在 ARM 上跑得很稳,但有个细节,就是编译 Redis 时默认的jemalloc分配器在部分 ARM 内核版本上可能告警。遇到的话可以切换回libc malloc,也就是编译时指定MALLOC=libc,代价是碎片率略高,但换来的是稳定性。
2.3 挂载参数初调:先把能用和不能用的事摸清楚
JuiceFS 的挂载参数非常多,第一次挂载时不要贪多,先用一组相对合理的参数跑起来,拿到基线数据再说。我当时的初始挂载参数如下:
mkdir -p /mnt/jfs ./juicefs mount \ --cache-dir /data/jfs-cache \ --cache-size 256000 \ --writeback \ --attr-cache 2 \ --entry-cache 2 \ --prefetch 4 \ --buffer-size 256 \ --io-size 131072 \ --max-uploads 128 \ --max-deletes 32 \ redis://10.10.1.5:6379/1 \ /mnt/jfs这几个参数后面全部还会细调,但首轮有几个问题必须先暴露出来:ARM 平台的 FUSE 驱动在新内核(5.4 以上)里应该默认开启fuse_async,也就是 FUSE 的异步 I/O,对吞吐提帮助很大;本地缓存盘如果没有用独立 NVMe(这里是/data/jfs-cache),缓存命中率再高,持续写入也扛不住。另外--buffer-size和--io-size我首轮给的是相对保守的值,主要是为了看瓶颈在哪,而不是一上来就开大参数掩盖问题。
挂载完以后,先验证基本功能:
ls /mnt/jfs dd if=/dev/zero of=/mnt/jfs/testfile bs=1M count=1024 conv=fsync确保基本的读写链路通,然后再往 MLPerf 方向走。
3. 基于 MLPerf 的基准测试流程与性能基线
3.1 MLPerf ResNet50 的数据准备:把 tfrecord 文件放对位置
MLPerf Training 的 ResNet50 官方流程要求先将 raw image(JPEG)转换成 tfrecord 格式,转换完成后会生成若干个大的二进制文件,总大小按数据集不同差不多 120GB 到 140GB。这一步有两个存储相关的点需要先想清楚:
第一,转换过程本身是一个典型的“小文件随机读 + 大文件顺序写”混合负载。JPEG 小文件如果放在 JuiceFS 上,读的时候要走网络拉取对象存储,速度会很慢,所以我建议先在本地临时目录做转换(本地 NVMe 足够快),转换完成后把 tfrecord 一次性灌进 JuiceFS。这一步相当于预先“暖”了 JuiceFS 的写路径。
第二,tfrecord 文件比较大(我记得单文件接近 60MB),灌进 JuiceFS 后要注意对象存储那边的分片大小。如果对象存储的分片阈值设置不合适,小分片太多会导致后续读取时 HTTP 请求数暴增,这对 ARM 服务器尤其不友好,因为它本身的网络吞吐未必能跑满万兆。
实际导入语气的命令像这样:
# 先把本地生成的 tfrecord 目录拷入 JuiceFS nohup cp -r /data/imagenet/tfrecord /mnt/jfs/imagenet/ &这个过程本身就是一次很好的写性能压测,我第一轮跑完看到写吞吐能稳定在 380MB/s 左右,整体来说在可接受范围,但离 NVMe 裸盘的极限(大概 1.5GB/s 以上)还有不少差距。
3.2 首轮 MLPerf 测试:拿到真实的瓶颈分布
MLPerf 官方实现(GitHub 上的 mlperf/training 仓库)跑 ResNet50 会有一个数据加载的瓶颈预警,如果存储层跟不上,最直接的表现是训练全程的吞吐不稳定,GPU 利用率曲线像锯齿一样,每隔一段时间就掉下去。这其实不是 GPU 的问题,而是数据管道在某个环节“断供”了。
首轮测试我记录了几个关键指标:
- 训练数据读取阶段:JuiceFS 的读吞吐从 120MB/s 到 550MB/s 之间剧烈抖动,平均约 240MB/s,GPU 利用率平均只有 65% 左右。
- checkpoint 写入阶段:每次写 checkpoint (大约 90MB 左右)耗时平均 6.5 秒,虽然不算离谱,但训练过程中每 10 个 epoch 左右就会卡一次,整体节奏被打断。
- 元数据操作:测试目录下文件数量约 15 万个,
ls -l要 3 秒以上,说明元数据路径存在明显瓶颈。
这个结果在意料之中,因为 ARM 服务器上的 FUSE 驱动、网络栈、缓存都要从默认状态开始调。但我特意留了一个基线数据,就是为了后面每一步调优都能看到收益。
提示:跑 MLPerf 前,先确认你的训练框架(这里用了 PyTorch)编译时是否开启了 ARM 相关的指令集优化。用官方预编译的 PyTorch ARM 包通常没问题,但如果是自己从源码编的,一定要加
-march=armv8.2-a+fp16+rcpc之类的参数,否则 CPU 端的数据预处理会拖后腿。
3.3 基线数据的分析方法
拿到基线数据后,我本能的反应是直接在 JuiceFS 层面加大缓存参数,但这往往会被存储层的真实瓶颈掩盖。更靠谱的做法是把“存储层性能”和“MLPerf 训练性能”分成两个维度单独评估:
存储层看几个指标:JuiceFS 客户端的缓存命中率、对象存储的请求延迟、元数据库(Redis)的 ops/s、本地缓存盘的 IO 吞吐。这些数据可以通过 JuiceFS 自带指标接口(默认 9567 端口的prometheus格式指标)拉取,比如juicefs_bucket_request_seconds、juicefs_cache_hit_bytes、juicefs_meta_ops_duration_seconds这类指标可以精确定位瓶颈。
训练层看几个指标:训练吞吐(images/sec)、GPU 平均利用率、训练每个 epoch 的耗时标准差。如果 epoch 耗时标准差很大,说明存储抖动已经传导到训练流程的上层。
这两个维度的数据要放在同一个时间轴上对比。我后来发现一个规律:每次 GPU 利用率掉下来之前,JuiceFS 的cache miss指标都会先出现一个尖峰,说明训练框架去读的文件不在缓存里,触发了网络回源。这个对应关系非常有用,后面调参就按照“先保证 cache hit,再谈其他优化”的顺序走。
4. 系统性调优实战:从存储参数到操作系统逐层拆解
4.1 JuiceFS 参数细调:找准 ARM 平台吃硬件配置的关键参数
第一轮基线拿到后,我开始逐项调整 JuiceFS 的挂载参数。这个阶段的重点是让本地的 NVMe 缓存盘发挥最大价值,同时让元数据操作不再成为瓶颈。
--cache-size从 256000(约 250GB)调到 320000(约 312GB),接近本地缓存盘的容量上限。这一步的核心逻辑是尽可能把所有训练数据文本文件(大约 130GB)都装进本地缓存。如果缓存盘撑不住,就要考虑扩大容量或者优化缓存淘汰策略。实测下来,调大 cache size 后,读缓存的命中率大约从 62% 提升到了 74%,训练阶段的抖动明显缓解,GPU 利用率平均升到了 72% 左右。
--writeback参数是这次调优最关键的胜负手。默认情况下,JuiceFS 的写请求要先写到对象存储才算完成,这意味着每次 checkpoint 写入都需要跨网络传输,延迟非常不稳定。开启 writeback 后,写请求先落到本地缓存盘,客户端异步上传到对象存储,对上层应用来说写入延迟大幅降低。Checkpoint 的平均写入耗时从 6.5 秒降到了 1.2 秒左右,这是一个质的提升。
但这有个前提:必须保证本地缓存盘有足够的容量和可靠性。JuiceFS 的 writeback 模式下,如果缓存盘损坏,尚未上传的数据会丢失。所以生产环境一定要给缓存盘做冗余,测试环境也要注意别把缓存盘塞满导致写阻塞。
--attr-cache和--entry-cache从 2 调整为 5(秒)。在 AI 训练场景,文件元数据和目录项的变动频率非常低,调大这两个数值能显著减少元数据引擎的访问次数。实测调整后,文件 stat 类操作延迟降低了约 40%,MLPerf 的文件遍历阶段明显快了。
--prefetch从 4 调整到 8。这个参数控制预读并发度,越大越能提前拉取数据。但要注意不能无限调大,因为 ARM 平台的内存带宽有限,过大的预读并发会挤占计算资源。我这里测试到 8 是收益递增的点,到 16 时内存占用明显增加但吞吐不再提升,反而带来了 GC 压力。
--io-size调整为 262144(256KB)甚至 524288(512KB)。JuiceFS 在读取大文件时会按照这个设定值拆分 I/O 请求。MLPerf 的 tfrecord 文件都在几十 MB 级别,属于典型的大文件顺序读,增大 I/O 块能让内核的 readahead 机制更好发挥作用,实测读吞吐从基线时的 240MB/s 提升到了 680MB/s。
--buffer-size调整为 512。这个参数控制读写缓冲区的大小,对吞吐影响不大,但对时延的稳定性有帮助,尤其是当对象存储的响应时间出现波动时,大缓冲区能起到“缓冲池”的调节作用。
4.2 FUSE 参数和网络协议栈的 ARM 专项调整
JuiceFS 通过 FUSE 挂载到/mnt/jfs,FUSE 本身在 ARM 平台上的行为与 x86 有差异。重点看两个参数:
第一个是/etc/fuse.conf里的user_allow_other,这个跟性能无关,但多用户场景下必须开,否则其他用户访问挂载点会报权限错误。第二个是内核 FUSE 模块的max_background参数。FUSE 请求在后台线程池中处理,如果线程池太小,高并发 I/O 会被排队阻塞。ARM 平台上,默认的max_background可能只有 12,我建议把它调到 64(你可以在/sys/module/fuse/parameters/max_background里查看和设置,但这需要重新加载 FUSE 模块才生效,所以我一般选择在开机启动脚本里直接写入)。
再往下是网络协议栈。对象存储可能存在远端(这里用的是内部的 MinIO 集群),网络传输的性能直接决定了缓存 miss 时的回源速度。ARM 平台上的网卡多队列和多核中断绑定与 x86 不完全一样,需要做一些针对性的配置。
我的做法是用ethtool看网卡支持多少个 RX 队列,然后用irqbalance配合taskset把不同的中断号绑定到不同的 CPU 核心上。ARM 平台的 NUMA 拓扑和 x86 不同,内存访问延迟和带宽在不同核心之间差异较大,绑核过程中要先确认网卡所在的 NUMA node,再把对应核绑到同一 node。否则网络中断跨节点访问内存,性能立刻衰减。
另外,net.core.rmem_max和net.core.wmem_max这两个 socket 缓冲区参数我在 ARM 平台设到了 134217728(128MB),net.ipv4.tcp_rmem和net.ipv4.tcp_wmem设置如下 3 组值:4096 87380 67108864,减少在网络延迟高时的吞吐波动。
4.3 操作系统级设置:让 ARM 处理器的行为更适配存储工作负载
AR 处理器的频率调度策略和 x86 不完全一样,鲲鹏 920 的cpufreq驱动在默认的ondemand策略下,频率会频繁切换,对稳定性要求高的存储负载很不友好。建议将 CPUfreq 调为performance策略:
cpupower frequency-set -g performance这个命令需要linux-cpupower(或linux-tools)工具包。如果服务器上没有装,也可以直接写进/etc/rc.local或者 systemd 服务里,确保重启后仍然生效。
然后是虚拟内存参数。JuiceFS 的写缓存和 FUSE 层都会大量使用 page cache,vm.dirty_ratio和vm.dirty_background_ratio控制的是脏页写回内存的时机。ARM 平台的内存带宽通常比 x86 窄,如果把vm.dirty_ratio设得太高,脏页会在某个时间点集中刷盘,导致 I/O 尖峰。这里我把vm.dirty_ratio设为 10,vm.dirty_background_ratio设为 3,让脏页更早、更平缓地刷盘,避免尖峰。
另一个值得关注的是transparent_hugepage。ARM 平台对 THP 的支持在不同内核版本上表现不一致,开启 THP 有时会带来明显的延迟尖峰,尤其是在 FUSE 和网络栈一起工作的场景。我建议在/etc/default/grub的内核启动参数里加上transparent_hugepage=never,然后update-grub并重启。这项改动对全局稳定性有正面影响,尤其是在 Redis 元数据引擎的内存分配环节。
4.4 对象存储端的配套调优
JuiceFS 的性能不只有客户端决定,对象存储的行为同样关键。我用的是 MinIO 集群,首轮测试就发现一个典型问题:JuiceFS 向对象存储写入时会按照--upload-limit限制的最大并发数发送分片上传请求,而 MinIO 端的默认配置可能导致分片上传排队,写入延迟被拉高。
解决方向有两个:一是调高--max-uploads和--upload-limit,让客户端发送更多并发分片请求;二是优化 MinIO 端的磁盘调度,把对象存储所在的磁盘预先做fstrim和块设备对齐。但要注意,过高的并发不一定带来线性收益,它受限于 ARM 服务器的网络带宽和网卡队列数。我实测了这一条:--max-uploads从 64 调到 128 之后,写入吞吐提升了约 20%,再往上调到 256 时,吞吐没有继续提升,反而因为内存占用增加导致 GC 频繁。
MinIO 本身的部署也做了一点调整:把 buckets 的分区从默认的一个目录拆成多个子目录,同时开启mc admin config set里的etcd相关参数(如果用了 etcd 做分布式锁)。没有 etcd 的话,保持默认就行。
4.5 关键调优后的 MLPerf 复测结果
整套参数调整完成后,我重新跑了同样的 MLPerf ResNet50 子项,结果如下:
- 训练数据读取阶段:吞吐在 620MB/s 到 850MB/s 之间波动,平均约 760MB/s,GPU 利用率稳定在 88% 左右。
- checkpoint 写入阶段:平均耗时 1.3 秒,基本感觉不到停顿。
- 元数据操作:
ls -l目录耗时降至 0.4 秒左右。 - 整体训练耗时:比基线值缩短了约 31%。
看起来数字挺喜人的,但这套配置并不适合在所有环境里照搬。ARM 服务器的 CPU 频率、内存带宽、缓存盘性能差异很大,参数要基于现场实测数据来定。
5. 常见问题与排查技巧实录
5.1 训练过程 GPU 利用率周期性掉零
这是一个很有代表性的问题。训练刚开始的几个 epoch 时间还算稳定,但每跑一段时间就会出现 GPU 利用率瞬间掉到 0,持续几秒后恢复。我用nvidia-smi dmon和 JuiceFS 指标接口并行观测,发现每次掉零的时间点都恰好和本地缓存盘出现writeback高峰的时间吻合。
根因是--writeback模式下,JuiceFS 把写缓存的数据异步刷到对象存储时,会占用一部分本地磁盘 I/O 带宽,而 FUSE 层在读训练数据时也在使用同一个磁盘。当两者并发时,NVMe 盘的队列深度被打满,读回来的延迟随即上升。
解决办法是在挂载时给读缓存和写缓存分配不同的缓存目录(JuiceFS 支持指定多个缓存目录),并且利用 NVMe 的多命名空间能力或者再加一块盘,把读写路径物理分离。如果条件不允许,可以采用更温和的策略,比如限制--max-uploads的并发,给读操作留带宽。
5.2 ARM 平台下 JuiceFS 客户端 CPU 占用异常偏高
有段时间,训练过程中 JuiceFS 客户端进程的 CPU 使用率接近 90%,这个明显不正常。用perf top排查后发现热点集中在 FUSE 的 read 路径和内部 JSON 序列化上,尤其是指标上报接口频繁被拉取。
JuiceFS 默认会把 Prometheus 指标暴露在 9567 端口,如果你有一套监控系统每 3 秒拉一次指标,而指标格式里包含大量对象存储请求延迟的直方图数据,JSON 序列化的开销就会放大。解决方法是把指标抓取间隔放宽到 15 秒,或者直接在客户端启动参数里关掉不必要的指标模块。
另一个 CGI 问题是 ARM 平台上的日志写入,如果启用了 debug 日志,日志内容会进入同一套 I/O 路径,一定程度上挤压正常读写。我建议生产环境把--log模块设为info级别,并让日志落到独立的磁盘分区上,避免和缓存盘竞争。
5.3 元数据引擎 Redis 的延迟尖峰
Redis 在 ARM 上本身跑得很稳,但当你把 JuiceFS 的--attr-cache和--entry-cache调大之后,元数据请求的频率会骤降,理论上 Redis 的负载应该下降。可我在调参后却观察到 Redis 的set操作延迟偶尔出现 300ms 以上的尖峰,很反常。
后来定位到是 Redis 开启了过期键主动删除策略,在定期删除任务运行时,CPU 瞬时飙高,影响了命令处理延迟。解决方式有两个:一是关闭主动过期删除,依赖惰性删除(可能导致临时性内存增长);二是给 Redis 单独绑定两个 CPU 核心,避免与 JuiceFS 客户端抢 CPU 资源。我最终选择了后一种方案,用taskset把 Redis 主进程固定在两个指定核上,延迟尖峰基本消失。
5.4 常见问题速查表
| 现象 | 可能原因 | 排查命令 | 解决方案 |
|---|---|---|---|
| GPU 利用率周期性掉零 | 读写缓存共用一块盘,I/O 竞争 | iostat -x 1观察本地盘 util | 读写缓存分目录/分盘;调低--max-uploads |
| JuiceFS 客户端 CPU 过高 | Prometheus 指标频繁拉取;debug 日志 | perf top查看热点 | 放宽指标采集间隔;日志级别调为 info |
| Redis 延迟尖峰 | 主动过期任务引发 CPU 尖峰;核间竞争 | redis-cli info commandstats | Redis 固定核;改为惰性删除 |
| 缓存命中率上不去 | cache-size 不够或淘汰策略不匹配 | 查看juicefs_cache_hit_ratio指标 | 扩大 cache-size;调大--attr-cache/--entry-cache |
| checkpoint 写得很慢 | 未开 writeback 或上传并发不足 | JuiceFS 指标中juicefs_object_write_seconds | 开启 writeback;调大--max-uploads |
5.5 一个容易被忽视的点:ARM 平台的内存分配器行为
经验之谈,最后一个容易被忽视的点其实是 Go 运行时内存分配器(TCMallocvsjemalloc的 Go 实现)在 ARM 平台的 GC 行为。JuiceFS 这个 Go 程序在 ARM 平台上跑的 GC 频率明显高于 x86,原因可能是 ARM 的内存带宽较窄,导致内存分配和回收的吞吐受到了限制。
我当时的做法是通过环境变量限制 Go GC 的内存目标:
export GOGC=60这会让 GC 更频繁地运行,但每次暂停的时间更短,对于 JuiceFS 这种需要低延迟响应的场景更有利。如果你跑下来发现 JuiceFS 内存持续走高或响应不稳定,可以从这个角度试一下。
结束语
整个调优过程持续了大约两周,最大的体会是:ARM 平台本身不是性能天花板,真正限制性能的往往是周边生态对 ARM 适配的疏忽。JuiceFS 这种 Go 语言项目跨平台支持已经很成熟,但对 CPU 频率、内存带宽、磁盘并发、网络队列这些底层因素的敏感度依然很高。每次调参之前先想清楚它在哪个路径上生效,是缓存层、元数据层、网络层还是本地盘 I/O 层,然后拿指标说话,别凭感觉硬调。
最后再分享一个小技巧:JuiceFS 首次把大规模数据集导入时,尽量在非训练时间段做,同时用一个独立脚本监控导入速率和缓存盘剩余空间。如果导入过程中缓存盘被写满,JuiceFS 会在缓存空间不足时退化成直写,这时候的导入速率会大幅下降,但很多人在意训练卡不卡,对导入过程反而没什么感知——这其实是我们后面发现缓存命中率上不去的一个深层原因,当时是导入早期把缓存盘“提前污染”了。提前规划好导入时段的参数,可以省掉后面很多排查成本。