PyTorch Dataset 应避免在 init 中加载全部数据,而应在 getitem 中按需读取以防止 OOM;需保证 getitem 原子性、无状态、可重入;len() 必须 O(1) 返回预统计值;流式数据应选用 IterableDataset。

为什么不能直接把所有数据 load 到内存里
PyTorch 的 Dataset 类设计初衷就是避免一次性加载全部样本——尤其当数据是 TB 级图像、长文本或视频帧时,__getitem__ 必须按需读取。常见错误是写成:在 __init__ 里用 np.load() 或 cv2.imread() 把所有路径对应文件全读进内存,结果 OOM 直接崩掉。
- 数据路径列表(如
self.img_paths)可以存为 list 或 mmap 文件索引,但像素/特征本身不加载
- 若用 HDF5 或 LMDB,确保只 open 句柄,不在
<strong>init</strong> 中 read() 全量内容
- 多进程 dataloader(
num_workers > 0)下,每个 worker 会单独调用 <strong>init</strong>,重复加载元信息不算错,但重复加载数据体就是灾难
如何安全实现 __getitem__ 并支持多进程__getitem__ 是核心入口,必须满足:原子性、无状态、可重入。典型坑是共享文件句柄或全局缓存没加锁。
- 每次调用
<strong>getitem</strong> 都应独立打开/关闭文件(如用 with Image.open(path) as img:),别复用类属性的 self.fh
- 若用
torchvision.io.read_image(),它内部已做 buffer 复用,比 PIL.Image.open() 更适合多 worker 场景
- 对于分片存储(如
data_001.pt, data_002.pt),用 shard_id = idx // shard_size 定位文件,再算局部索引,避免所有 worker 同时打开同一文件导致 IO 争抢
- 若需预处理(resize/augment),放在
<strong>getitem</strong> 内而非 <strong>init</strong>,否则无法利用 transforms.Compose 的随机性
怎么让 Dataset 支持 len() 且不扫描全量文件len() 被 dataloader 用于计算 batch 数量,必须是 O(1) 时间返回。错误做法是每次返回 len(os.listdir(...)) 或遍历所有路径。
- 最佳实践:初始化时从配置文件或数据库读取
self.total_samples = 1234567,这个数应由数据生成 pipeline 预先统计并固化
- 若用 Parquet 或 Arrow 格式,可直接读
table.num_rows;若用 TFRecord,需提前写入 index 文件记录 record 数
- 不要用
glob.glob("*.jpg") 在 <strong>len</strong> 里实时扫描——路径多时耗时秒级,且并发时可能漏文件
何时该用 IterableDataset 而不是 Dataset
当数据源是流式(Kafka、S3 prefix、数据库游标)或根本无法预知总长度(实时日志、在线爬虫),Dataset 的随机访问假设就失效了。
-
IterableDataset 没有 <strong>len</strong> 和 <strong>getitem</strong>,只有 <strong>iter</strong>,dataloader 自动按顺序消费
- 它天然适配
num_workers=0(因无法 random shuffle),若需 shuffle,得在 <strong>iter</strong> 内用 itertools.islice + 缓冲池,或依赖上游 shuffle(如 S3 的 list_objects_v2 返回乱序)
- 注意:
DataLoader(dataset, shuffle=True) 对 IterableDataset 无效,会静默忽略
self.img_paths)可以存为 list 或 mmap 文件索引,但像素/特征本身不加载 <strong>init</strong> 中 read() 全量内容 num_workers > 0)下,每个 worker 会单独调用 <strong>init</strong>,重复加载元信息不算错,但重复加载数据体就是灾难 __getitem__ 是核心入口,必须满足:原子性、无状态、可重入。典型坑是共享文件句柄或全局缓存没加锁。
- 每次调用
<strong>getitem</strong>都应独立打开/关闭文件(如用with Image.open(path) as img:),别复用类属性的self.fh - 若用
torchvision.io.read_image(),它内部已做 buffer 复用,比PIL.Image.open()更适合多 worker 场景 - 对于分片存储(如
data_001.pt,data_002.pt),用shard_id = idx // shard_size定位文件,再算局部索引,避免所有 worker 同时打开同一文件导致 IO 争抢 - 若需预处理(resize/augment),放在
<strong>getitem</strong>内而非<strong>init</strong>,否则无法利用transforms.Compose的随机性
怎么让 Dataset 支持 len() 且不扫描全量文件len() 被 dataloader 用于计算 batch 数量,必须是 O(1) 时间返回。错误做法是每次返回 len(os.listdir(...)) 或遍历所有路径。
- 最佳实践:初始化时从配置文件或数据库读取
self.total_samples = 1234567,这个数应由数据生成 pipeline 预先统计并固化
- 若用 Parquet 或 Arrow 格式,可直接读
table.num_rows;若用 TFRecord,需提前写入 index 文件记录 record 数
- 不要用
glob.glob("*.jpg") 在 <strong>len</strong> 里实时扫描——路径多时耗时秒级,且并发时可能漏文件
何时该用 IterableDataset 而不是 Dataset
当数据源是流式(Kafka、S3 prefix、数据库游标)或根本无法预知总长度(实时日志、在线爬虫),Dataset 的随机访问假设就失效了。
-
IterableDataset 没有 <strong>len</strong> 和 <strong>getitem</strong>,只有 <strong>iter</strong>,dataloader 自动按顺序消费
- 它天然适配
num_workers=0(因无法 random shuffle),若需 shuffle,得在 <strong>iter</strong> 内用 itertools.islice + 缓冲池,或依赖上游 shuffle(如 S3 的 list_objects_v2 返回乱序)
- 注意:
DataLoader(dataset, shuffle=True) 对 IterableDataset 无效,会静默忽略
self.total_samples = 1234567,这个数应由数据生成 pipeline 预先统计并固化 table.num_rows;若用 TFRecord,需提前写入 index 文件记录 record 数 glob.glob("*.jpg") 在 <strong>len</strong> 里实时扫描——路径多时耗时秒级,且并发时可能漏文件 Dataset 的随机访问假设就失效了。
-
IterableDataset没有<strong>len</strong>和<strong>getitem</strong>,只有<strong>iter</strong>,dataloader 自动按顺序消费 - 它天然适配
num_workers=0(因无法 random shuffle),若需 shuffle,得在<strong>iter</strong>内用itertools.islice+ 缓冲池,或依赖上游 shuffle(如 S3 的 list_objects_v2 返回乱序) - 注意:
DataLoader(dataset, shuffle=True)对IterableDataset无效,会静默忽略
真正麻烦的不是写法,是元数据一致性——比如你改了数据生成逻辑但忘了更新 self.total_samples,或者 HDF5 文件被另一个进程 truncate 了,<strong>getitem</strong>(idx) 才会报 IndexError: index 123456 out of bounds。这种错不会在 init 阶段暴露,得靠线上采样验证。


















