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 的底层接口按需读取单个样本往往更快。