WebDataset 以 TAR 分片 + 顺序流式读取替代海量小文件的随机访问,解决大规模训练的数据 I/O 瓶颈,并天然适配分布式训练与对象存储。

定位与动机

WebDataset 是基于 TAR 归档的数据加载库:把数百万小文件打包成分片 TAR,训练时流式顺序读取。wds 格式本质是遵循额外命名约定的 tar 文件,一般不压缩,以支持流式解码。它实现的是 iterable-style 接口,是 Dataset 与 DataLoader 体系中 IterableDataset 路线的代表——没有索引、按序产出样本,因此绕开了随机访问的全部代价。

特性传统文件系统WebDataset
访问模式随机访问,高延迟顺序读取,高吞吐
存储效率小文件元数据开销大TAR 容器摊薄元数据
分布式支持需复杂协调天然按分片划分
网络传输海量小请求效率低大文件流式传输

与 HDF5 数据格式 相比:HDF5 提供分层索引与部分随机读取,但单文件写入并发差、云存储上表现不佳;WebDataset 牺牲随机访问,换来对对象存储(S3/GCS/Azure)与 HTTP 的原生适配——数据集就是一组可直接 curl 的 URL。

核心原理

顺序读取:机械硬盘随机寻道使随机读取吞吐仅为顺序读取的约 1/100;即使 SSD 与对象存储,顺序大块 I/O 也远优于海量小请求。WebDataset 将训练负载的整体访问模式转化为顺序 I/O。

分片机制:数据集切为多个 TAR 分片,每片含数千样本:

  • 不同分片由不同 worker/节点并行读取
  • 分布式训练总览 中的数据并行各 rank 处理不同分片子集,无需中心协调
  • 单分片损坏只损失局部数据

样本组织规范:同一样本的所有文件共享同一前缀 key,以扩展名区分类型:

images17/image194.left.jpg
images17/image194.right.jpg
images17/image194.json

读取后聚合为字典,__key__ 为样本 key,扩展名即字段名:

{"__key__": "images17/image194", "left.jpg": b"...", "right.jpg": b"...", "json": b"..."}

写入:TarWriter

import webdataset as wds
import json
 
with wds.TarWriter("shard-000000.tar") as sink:  # 每写数千样本换一个分片文件
    for i, (image_data, label, metadata) in enumerate(samples):
        sink.write({
            "__key__": f"sample{i:06d}",       # 同一样本所有文件的公共前缀
            "jpg": image_data,                 # bytes 直接写入
            "cls": str(label).encode(),
            "json": json.dumps(metadata).encode()
        })

读取管线

WebDataset 用链式组合子构建处理管线,每个 stage 都是 iterable:

import webdataset as wds
import torch
from torchvision import transforms
 
preprocess = transforms.Compose([
    transforms.RandomResizedCrop(224),
    transforms.ToTensor(),
])
 
dataset = (wds.WebDataset("dataset-{000000..000099}.tar")  # Brace 展开 100 个分片 URL
    .shuffle(1000)                # 缓冲区洗牌:缓冲 1000 样本再随机吐出
    .decode("pil")                # 按扩展名自动解码(jpg→PIL,json→dict,cls→str)
    .to_tuple("jpg", "cls")       # 字典 → (image, label) 元组
    .map_tuple(preprocess, int)   # 对元组各分量分别施加变换
    .batched(32)                  # 产出已堆叠的 batch tensor
)

常用组合子还有 select(按谓词过滤样本,如 .select(lambda s: "positive" in s["txt"]))与 rename/map_dict(字段级变换);多模态样本直接在 to_tuple 列出各模态扩展名即可(如 to_tuple("jpg", "wav", "txt"))。

管线内部已 batch,DataLoader 须设 batch_size=None

loader = torch.utils.data.DataLoader(dataset, batch_size=None, num_workers=4)

 
关键点:`shuffle` 是有界缓冲区洗牌而非全局洗牌(流式无法回看),缓冲区大小决定 shuffle 强度;跨 epoch 需要 `shuffle` 缓冲 + 分片级随机化共同保证充分混合。
 
## 分布式分片
 
```python
dataset = (wds.WebDataset("dataset-{000000..012345}.tar",
        resampled=True,                 # 无放回 → 有放回采样:epoch 长度独立于数据集大小
        nodesplitter=wds.split_by_node) # 按 rank/node 交错分配分片,互不重叠
    .shuffle(10000)
    .decode("torchrgb")                # 直接解码为 CHW float tensor
    .to_tuple("jpg", "cls")
    .batched(64)
)
 
loader = wds.WebLoader(dataset, batch_size=None, num_workers=8)

两种策略:默认按节点切分分片子集(strict,见一次);resampled=True 改为独立随机重采样,每个 worker 看到不同的分片流,适合超长训练与不均匀环境。WebLoader 封装了多 worker 流的再聚合与 prefetch。

分片大小与缓存策略

存储类型推荐分片大小
本地硬盘256MB–1GB
网络存储1–4GB
云对象存储4–16GB

原则:分片数应远大于 worker 总数(保证负载均衡),单片足够大以摊薄请求开销。

远程数据集可先落地本地缓存,后续 epoch 直接读本地:

dataset = (wds.WebDataset("https://example.com/dataset-{000000..000999}.tar")
    .cache_dir("./cache")               # 首次访问下载分片到本地目录
    .cache_size(10 * 1024**3)           # LRU 上限 10GB
    .shuffle(10000)
    .decode("pil")
)

cache_dir 适合分片可完整落盘的场景;不便缓存时可退化为 pipecmd 流式解压(如 zstd 管道)。

随机读取:wids

wds 为流式设计,随机访问需另建索引。wids(WebDataset Indexed Shards)为分片集合生成 index 文件(记录每样本的 tar 内偏移),从而支持按样本 key 随机抽取、map-style 访问——代价是需预先建索引且失去纯流式的简洁性。若已知样本所在分片路径与 key,直接用 webdataset 的底层接口按需读取单个样本往往更快。