根本原因是num_parallel_calls默认为None导致map()串行阻塞;应设为tf.data.AUTOTUNE,配合cache()→shuffle()→batch()→prefetch()正确顺序,并用profiler定位IteratorGetNext耗时。
tf.data pipeline 为什么卡在
里
常见现象是训练刚开始几轮很快,后面
耗时陡增,
像没起作用。根本原因不是 CPU 不够,而是默认
是
(即单线程执行),尤其当你的预处理含
、
或自定义 Python 函数时,会退化成串行阻塞。
实操建议:
显式设
,别用数字硬编码(不同机器核数不同)
如果用了
,必须加
,否则它自动降级为单线程
避免在
里做重 IO 操作(比如每次读 config 文件),提前加载到内存或用
用
放在
后、
前——对小数据集有效;若内存吃紧,改用
写磁盘
batch() 之后再
还是之前
顺序错了性能直接打五折。典型错误是写成
,这会让 prefetch 提前拉取未 batch 的原始样本,GPU 等着喂数据时反而在等 CPU 拼 batch。
正确链路必须是:
。
关键点:
够用,设更大(如
)未必加速,还可能吃光内存
如果模型每步需要多个 batch(如梯度累积),
无法替代逻辑,得靠外层循环控制
用
确认数据集大小,避免
在无限数据集上无意义堆积
用
就一定慢吗
不一定,但极易踩坑。generator 本身不支持并行,哪怕你加了
,TF 也只会在 generator yield 后才并行 map,yield 过程仍是单线程。
适用场景仅限:数据源无法用 TF 原生 IO(比如实时爬虫、数据库游标、非标准二进制格式)。
提速要点:
generator 函数体内禁止任何阻塞操作(如
、同步 HTTP 请求),全改异步+队列缓冲
用
+
替代多目录遍历,比 generator +
快 2–3 倍
如果 generator 返回的是 numpy,务必在
中转成
,别依赖自动转换——后者可能触发隐式 copy
验证流水线是否真跑起来了
光看 loss 下降没用。真正瓶颈常藏在「CPU 利用率 100% 但 GPU 利用率 30%」这种错配里,说明数据供给不上。
快速诊断方法:
加
开启图优化(TF 2.9+ 默认开启,旧版需手动)
用
和
测单次迭代耗时,排除首次编译干扰
开 profiler:
,跑 2–3 step 后看「Input Pipeline Analyzer」,重点盯
和
耗时占比
如果
占比 >40%,基本就是数据流水线拖后腿;低于 10% 但训练仍慢,问题大概率在模型侧(如
未正确 jit)
最易被忽略的是:本地 SSD 读取速度远高于 NFS 或云存储,但很多人把数据放 NAS 上调试,调完流水线发现换到本地快一倍——IO 层的延迟根本不在 tf.data 控制范围内。
dataset.map()map()prefetch()num_parallel_callsNonetf.io.read_filetf.image.decode_jpegnum_parallel_calls=tf.data.AUTOTUNEtf.py_functionnum_parallel_callsmap()tf.lookup.StaticHashTabledataset.cache()map()shuffle()cache('/path/to/cache')prefetch()dataset.prefetch().batch()map() → cache() → shuffle() → batch() → prefetch()prefetch(1)tf.data.AUTOTUNEprefetch()dataset.cardinality().numpy()prefetch()tf.data.Dataset.from_generator()num_parallel_callstime.sleeptf.data.Dataset.from_tensor_slices()interleave()list_filesmap()tf.convert_to_tensor()dataset = dataset.apply(tf.data.experimental.optimize())tf.data.experimental.cardinality(dataset).numpy()next(iter(dataset))tf.profiler.experimental.start('logdir')IteratorGetNextMapAndBatchIteratorGetNexttf.function