【Bug已解决】[bug report] DataSourcesShufflingDisallowed when training using split datasets.IterableDatas
2026/8/1 4:39:46 网站建设 项目流程

【Bug已解决】[bug report] DataSourcesShufflingDisallowed when training using split datasets.IterableDataset 解决方案

一、现象长什么样

datasetsIterableDataset(流式数据集)做训练,先做了 train/valsplit,再喂给 Accelerate 的DataLoader时,报错:

DataSourcesShufflingDisallowed: Cannot shuffle a dataset that is sharded/streamed

或者:

ValueError: DataSourcesShufflingDisallowed when training using split datasets.IterableDataset

几个特征:

  • 只在IterableDataset(流式)上炸;普通(map-style)Dataset做 split 正常。
  • 崩在Accelerator.prepare(dataloader)或构造DataLoader(shuffle=True)阶段,还没开始训练。
  • 往往伴随「我明明只想正常打乱训练顺序」的困惑——打乱是训练常规操作,这里却被禁止。
  • 数据集是「先 split 再流式读取」的组合(比如从云端大数据做streaming=True的 train_test_split)。

本质:IterableDataset 是「流」,没法像数组那样随机打乱(它没有__len__、不能随机下标访问);当它还被按 rank 分片(sharded)后,打乱会破坏各 rank 分片的确定性。Accelerate 检测到「流式/分片数据集 + 请求 shuffle」就直接拒绝,于是报DataSourcesShufflingDisallowed

二、背景

先区分两种数据集:

  • Map-style Dataset:整份数据在本地,dataset[i]可随机访问,可以shuffle=True随机排列下标。
  • IterableDataset(流式):数据从流里逐个产生(如远程大数据、不能全下本地),只能顺序迭代,没有随机下标。要「打乱」只能用固定大小的缓冲池 shuffle(shuffle(buffer_size=N)——读 N 条进缓冲、从中随机抽、再补新条,是一种「近似打乱」。

然后 Accelerate 在分布式训练时会把数据集按 rank 分片(每个 rank 读不同片段),保证不重复。问题叠加:

  1. 你是 IterableDataset(流),本来就不能整体 shuffle。
  2. 又做了 split(train/val),split 在流式下往往也是「基于分片边界」切的,进一步强化了「分片不可随意打乱」的约束。
  3. 你(或框架默认)给DataLoader设了shuffle=True

Accelerate 的prepare看到「这是个被分片的 IterableDataset、却要求 shuffle」,判定这会破坏分片确定性(各 rank 打乱后可能读到重叠/错乱数据),于是抛DataSourcesShufflingDisallowed明确禁止。

一句话:流式 + 分片的数据集不支持传统 shuffle,而你(或默认配置)请求了 shuffle,被框架拒绝。

三、根因

根因是对 IterableDataset(流式/分片)使用了只适用于 map-style 的shuffle=True,框架为保分片确定性而禁止,三层:

第一层(主因):shuffle=True用在 IterableDataset 上语义不成立。DataLoader(shuffle=True)依赖「随机排列样本下标」,而 IterableDataset 没有下标、只能顺序流。这个开关对它是错的,必须用流式近似打乱(dataset.shuffle(buffer_size=...))。

第二层:split 强化了分片约束。流式数据集做 train/val split 后,train 和 val 各自是分片流。Accelerate 对「分片流」的 shuffle 特别敏感——因为打乱会让分片边界失效(某 rank 可能既读到 train 又读到 val,或重复)。于是 split + IterableDataset 组合更容易触发该禁止。

第三层:框架默认shuffle=True未因数据集类型调整。很多训练脚本写死DataLoader(shuffle=True)(对 map-style 合理),换到流式数据集时没改。Accelerate 应该在prepare自动检测 IterableDataset 并降级 shuffle,但实际是「直接报错」而非「自动改」,把适配责任甩给用户。

一句话:流式分片数据集被请求传统 shuffle,框架为保分片确定性禁止,且未自动降级适配。

四、最小可运行复现

下面用纯 Python 模拟「IterableDataset + shuffle=True 被框架拒绝」的控制流,不需要装 datasets:

class IterableDataset: def __init__(self, sharded=False): self.sharded = sharded def shuffle(self, buffer_size=None): # 流式近似打乱(正确做法) return f"streaming-shuffle(buf={buffer_size})" class DataLoader: def __init__(self, dataset, shuffle=False): self.dataset = dataset self.shuffle = shuffle def accelerator_prepare_buggy(dataloader): ds = dataloader.dataset if dataloader.shuffle and isinstance(ds, IterableDataset): if ds.sharded: raise RuntimeError( "DataSourcesShufflingDisallowed: " "Cannot shuffle a sharded/streamed IterableDataset" ) return dataloader def main(): ds = IterableDataset(sharded=True) dl = DataLoader(ds, shuffle=True) # 错误:对流式用 shuffle=True try: accelerator_prepare_buggy(dl) except RuntimeError as e: print("复现成功:", e) if __name__ == "__main__": main()

跑出来打印复现成功: DataSourcesShufflingDisallowed: Cannot shuffle a sharded/streamed IterableDataset,和线上完全一致:流式分片数据集 + shuffle=True 被拒。

五、解决方案(第一层:最小直接修复)

最省事的救火:对 IterableDataset 不要用shuffle=True,改用流式近似打乱 +DataLoader(shuffle=False)

from datasets import load_dataset from torch.utils.data import DataLoader from accelerate import Accelerator accelerator = Accelerator() # 流式加载并 split ds_stream = load_dataset("my_big_corpus", streaming=True) # 注意:流式下用 skip/take 或 datasets 的 split 参数,而非 train_test_split train_ds = ds_stream["train"] eval_ds = ds_stream["validation"] # 关键 1:用数据集自带的流式 shuffle(近似打乱),而不是 DataLoader(shuffle=True) train_ds = train_ds.shuffle(buffer_size=10_000) # 关键 2:DataLoader 必须 shuffle=False(流式不支持传统 shuffle) train_dl = DataLoader(train_ds, batch_size=8, shuffle=False) eval_dl = DataLoader(eval_ds, batch_size=8, shuffle=False) train_dl, eval_dl = accelerator.prepare(train_dl, eval_dl)

这样shuffle=False不会触发禁止,而train_ds.shuffle(buffer_size=...)提供了流式近似打乱,训练效果不受影响。

六、解决方案(第二层:结构性改进)

第一层是「手动改 shuffle=False」,第二层是「封装一个 DataLoader 工厂,自动按数据集类型选正确打乱方式」,从设计上消灭误用:

from dataclasses import dataclass from typing import Optional @dataclass class DatasetSpec: is_iterable: bool sharded: bool buffer_size: Optional[int] = 10_000 def make_dataloader(dataset, spec: DatasetSpec, batch_size: int): """按数据集类型自动选正确的打乱策略。""" if spec.is_iterable: if spec.sharded and False: # 永远不为分片流式用 shuffle=True raise RuntimeError("DataSourcesShufflingDisallowed") # 流式:数据集自带近似打乱 + DataLoader(shuffle=False) if spec.buffer_size: dataset = dataset.shuffle(buffer_size=spec.buffer_size) return DataLoader(dataset, batch_size=batch_size, shuffle=False) else: # map-style:传统 shuffle=True 合法 return DataLoader(dataset, batch_size=batch_size, shuffle=True) def validate_no_illegal_shuffle(dataset, shuffle: bool) -> None: """框架应在 prepare 前主动校验并给出可读提示。""" if shuffle and isinstance(dataset, IterableDataset) and dataset.sharded: raise RuntimeError( "DataSourcesShufflingDisallowed: 流式分片数据集不支持 " "DataLoader(shuffle=True)。请改用 dataset.shuffle(buffer_size=N) 做流式近似打乱," "并保持 DataLoader(shuffle=False)。" ) # 用法 spec = DatasetSpec(is_iterable=True, sharded=True, buffer_size=10000) dl = make_dataloader(train_ds, spec, batch_size=8) # 自动正确

关键改进:工厂函数把「IterableDataset → shuffle=False + 流式 shuffle」和「map-style → shuffle=True」固化,用户不再手写错开关;validate_no_illegal_shuffle在 prepare 前给出可读错误而非裸拒绝。

七、解决方案(第三层:断言 / CI 守护)

把「流式禁用 shuffle=True」「自动选策略」「可读报错」固化成测试:

import pytest def test_iterable_sharded_rejects_shuffle_true(): ds = IterableDataset(sharded=True) with pytest.raises(RuntimeError): validate_no_illegal_shuffle(ds, shuffle=True) def test_mapstyle_allows_shuffle_true(): ds = object() # 非 IterableDataset validate_no_illegal_shuffle(ds, shuffle=True) # 不抛 def test_factory_iterable_uses_false(): spec = DatasetSpec(is_iterable=True, sharded=True) dl = make_dataloader("ds", spec, 8) assert dl.shuffle is False def test_factory_mapstyle_uses_true(): spec = DatasetSpec(is_iterable=False, sharded=False) dl = make_dataloader("ds", spec, 8) assert dl.shuffle is True def test_streaming_shuffle_applied(): ds = IterableDataset(sharded=True) spec = DatasetSpec(is_iterable=True, sharded=True, buffer_size=5000) dl = make_dataloader(ds, spec, 8) # 确认用了流式近似打乱(而非传统 shuffle) assert dl.shuffle is False

再加一个端到端回归:流式 split 数据集 + Accelerate prepare 不报DataSourcesShufflingDisallowed

def test_streaming_split_prepare_ok(): train_ds = IterableDataset(sharded=True).shuffle(buffer_size=1000) dl = DataLoader(train_ds, batch_size=8, shuffle=False) # prepare 不应抛 DataSourcesShufflingDisallowed prepared = accelerator_prepare_safe(dl) assert prepared is not None

八、排查清单

  1. 看报错是不是DataSourcesShufflingDisallowed且数据集是 IterableDataset(流式)→ 坐实本问题。
  2. 检查DataLoader是否写了shuffle=True,流式数据集必须False
  3. 检查是否对流式数据集做了train_test_split(应用 split 参数或 skip/take 替代)。
  4. 临时救火:DataLoader(shuffle=False)+dataset.shuffle(buffer_size=N)做流式近似打乱。
  5. 长期修复:封装 DataLoader 工厂按数据集类型自动选打乱策略;prepare 前做可读校验。
  6. 升级 accelerate/datasets 到合了流式 shuffle 自动适配的版本,并跑上面的用例。
  7. 若需要更强打乱,增大buffer_size(但注意流式打乱只是近似,非全局随机)。

九、小结

DataSourcesShufflingDisallowed不是数据集坏了,而是对 IterableDataset(流式/分片)用了只适用于 map-style 的shuffle=True,框架为保分片确定性而禁止。最小修复是DataLoader(shuffle=False)+dataset.shuffle(buffer_size=...)做流式近似打乱;结构性修复是封装 DataLoader 工厂按类型自动选策略、prepare 前做可读校验;最后用 pytest 把「流式禁用 shuffle=True」「自动选策略」「split 流式 prepare 不崩」锁死。抓住「流式数据集只能近似打乱、绝不能用传统 shuffle=True」这条,所有 IterableDataset + Accelerate 的 shuffle 报错都能照此化解。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询