在国产 NPU(如华为昇腾 Ascend、百度昆仑芯等)上进行大规模深度学习训练时,开发者常遇到一个痛点:计算单元(NPU)在等待数据,导致利用率低下。这种情况在处理海量小文件(如千万级的 ImageNet 图片)时尤为严重。由于分布式存储(Ceph, Lustre)在处理海量元数据请求时存在高延迟,NPU 强大的算力往往被阻塞在 IO 读取阶段。本文将介绍如何通过 WebDataset 序列化方案与多级分发机制,彻底解决这一难题。
1. 核心瓶颈分析
在传统的 Dataset 加载模式中,每个 Epoch 都会触发数百万次 os.stat 和文件 open 操作。在分布式网络存储环境下:
1. IOPS 爆炸:数千个进程同时请求小文件,元数据服务器(MDS)瞬间过载。
2. 随机读延迟:机械硬盘或低速网络在随机读取小文件时,吞吐量远低于顺序读。
2. 解决方案:数据流式序列化 (WebDataset)
最有效的方案是将数万个小文件合并为大块的 .tar 序列文件。这样,读取过程从“随机点读”变成了“顺序流读”,极大地降低了元数据开销。
步骤一:将原始数据集转换为 Shards
我们需要将数据打包成多个 100MB-1GB 左右的 .tar 包。以下是转换逻辑示例:
import webdataset as wds
import os
def convert_to_shards(src_dir, dest_dir, pattern=\"data-%06d.tar\", maxsize=1e9):
# 创建 WebDataset 写入器
sink = wds.ShardWriter(os.path.join(dest_dir, pattern), maxsize=maxsize)
for root, _, files in os.walk(src_dir):
for fname in files:
if fname.endswith(('.jpg', '.png')):
label = root.split('/')[-1]
with open(os.path.join(root, fname), 'rb') as f:
image_data = f.read()
# 写入 tar 包,key 为文件名,包含图像和类别
sink.write({
\"__key__\": fname.split('.')[0],
\"jpg\": image_data,
\"cls\": label.encode('utf-8')
})
sink.close()
步骤二:针对国产 NPU 的高效加载方案
在昇腾 Ascend NPU 上使用 PyTorch 时,可以配合 webdataset 库进行流式读取。其核心优势在于数据可以直接从网络流中解压并送入内存缓冲区,无需在本地产生临时小文件。
import torch
import webdataset as wds
from torchvision import transforms
# 定义预处理流
preprocess = transforms.Compose([
transforms.RandomResizedCrop(224),
transforms.ToTensor(),
])
# 构造 WebDataset 管道
# nodesplitter 确保分布式训练时每个 Rank 只读取部分数据
url = \"/mnt/nfs/dataset/data-{000000..000999}.tar\"
dataset = (
wds.WebDataset(url, shardshuffle=True)
.shuffle(1000)
.decode(\"pil\")
.to_tuple(\"jpg\", \"cls\")
.map_tuple(preprocess, lambda x: int(x))
)
# 使用 DataLoader 配合 NPU 训练
train_loader = torch.utils.data.DataLoader(
dataset, batch_size=128, num_workers=8, pin_memory=True
)
# 在 NPU 训练循环中使用
# device = torch.device('npu:0')
# for imgs, labels in train_loader:
# imgs = imgs.to(device)
# ... 训练逻辑 ...
3. 进阶:配置多级缓存 (Local Cache)
如果 NPU 服务器配备了本地 NVMe SSD,可以进一步开启本地缓存。WebDataset 支持将远端下载的 tar 包自动缓存到本地:
# 开启本地缓存,第二次迭代时将直接从 SSD 读取,不再占用网络带宽
dataset = wds.WebDataset(
url,
cache_dir=\"/scratch/my_dataset_cache\",
shardshuffle=True
)
4. 适配国产架构的注意事项
- 算子下沉:如果使用的是华为 MindSpore 框架,建议直接使用其内置的 MindRecord 格式,原理与 WebDataset 类似,但能更好地支持 Ascend 的算子下沉(Data Sink)模式。
- 进程数平衡:国产 NPU 的 CPU 核心数往往较多,建议 num_workers 设置为单卡对应 CPU 核数的 1/2 或 1/4,避免因过度并行的进程上下文切换导致 CPU 瓶颈。
总结
通过将“海量小文件”重构为“大块序列文件”,我们避开了分布式存储的元数据性能洼地。在国产 NPU 适配实践中,这一方案能有效消除 IO 瓶颈,使 NPU 的 TFLOPS 表现回归到理论预期。
汤不热吧