首页优化地图方法索引基础概念
返回技术汇总表 ↗

M033 · 独立详解

数据预处理、预取与异步搬运:让下一批输入及时到达

沿存储、CPU准备、主机缓冲、设备复制到计算的路径,定位输入瓶颈、预取深度和缓冲生命周期。

内存与数据搬运调度与并行详解已编写
视频讲解待制作

这篇文章的小视频尚未制作;可以先阅读下方文字详解。

本页目录

01 · 设备空闲,不一定是模型算得慢

一批训练数据到设备前,可能经过存储读取、解压、解析、分词、样本筛选、打包和Tensor整理,最后从主机内存复制到设备。只优化模型内核,无法消除输入迟迟不到造成的等待。

推理也有请求解析、分词和元数据准备,但输入不能在请求到来前凭空生成。训练数据可提前组织;在线服务则要兼顾新请求延迟和其他请求的公平性。

02 · 把输入链路的瓶颈拆开看

位置可能的瓶颈可以尝试的优化
存储/网络读取小文件、随机访问、远端抖动分片文件、顺序读取、本地缓存和合适并发。
CPU预处理分词、解析、解压或大量小对象批量处理、提前计算可复用结果、工作进程。
拼批/整理长度分布、复制碎片、重复转换长度分组、有效token预算、减少不必要复制。
主机缓冲数据尚未准备好、锁页成本高受控预取、复用合适缓冲和按后端处理锁页。
设备复制传输慢、提交后立刻阻塞批量搬运、可支持的异步复制与stream依赖。

缓存预处理结果时,要把Tokenizer版本、词表、模板、清洗/截断配置和数据版本纳入兼容条件。随机增强或随epoch变化的步骤不应无条件变成固定缓存。

03 · 预取不是少做工作,是早点做

没有预取时,设备算完批次1后,才开始准备批次2。预取则在当前批次计算期间准备下一批,使其在设备需要时已就绪。预取队列存放这些已准备但尚未消费的数据。

队列过浅可能被输入抖动耗尽;过深占用RAM、锁页内存或设备缓冲,却不会让持续慢于消费者的生产者无限提速。队列装满时应限制生产,让内存保持可控,这种机制称为背压。

教学示意 · 不代表硬件实测
生产者:读取/预处理 → 放入有容量上限的ready队列
消费者:取出就绪批次 → 复制 → 执行设备计算
队列空:消费者等待输入
队列满:生产者等待容量
# 缓存与排队提高容错余量,不能改变最慢阶段的长期速率。

04 · 四批数据如何形成输入流水

自设每批CPU准备3 ms、复制1 ms、设备计算6 ms,四批独立数据。一批接一批完全串行为4×(3+1+6)=40 ms;若三个阶段可独立推进、有足够缓冲且依赖正确,四批于28 ms完成。

准备、复制、计算:每批仍须按依赖推进

一个资源负责一个阶段,批次独立,缓冲充足;计算等自己的复制完成,复制等自己的准备完成。无运行时开销与争用,不代表实测。

批次1在0–3 ms准备、3–4 ms复制、4–10 ms计算;同时批次2可在3–6 ms准备。下一批计算仍要等自己的复制完成,并等设备计算资源空闲。

05 · pinned memory与non_blocking分别解决什么

在CUDA常见路径中,pinned memory是锁页主机内存,有利于设备DMA搬运。non_blocking=True请求减少主机等待,但“CPU调用提前返回”与“复制确实和计算同时执行”是两件事。

复制与计算要实际重叠,还需要后端支持、合适的复制执行资源、不同队列中可独立的工作和正确依赖。同一个stream里按顺序复制再计算,不能仅因加了non_blocking就宣布二者重叠。

在热点线程里临时pin_memory再复制,也要付出分配和数据复制成本,可能得不偿失。宜观察整个输入链路,而不是只测已经准备好的锁页数据。

依据:PyTorch · pin_memory与non_blocking使用指南 ↗

06 · 复制未完成,源缓冲不能被下一批覆盖

缓冲状态允许做什么危险操作
空闲写入本批CPU数据无
复制进行中保留源内容与目标容量,等待复制完成重填主机源、释放仍被使用的内存。
设备输入已就绪按依赖启动计算在计算结束前覆写设备输入。
消费完成回收对应缓冲给后续批次错误地只等复制结束就复用设备输入。

可以让不同批次使用不同槽位,例如双缓冲,但每个槽位都要知道复制和计算何时完成。GPU到CPU的异步结果复制也要等完成后才能读;接口返回不等于CPU结果已可用。

框架管理的Tensor引用、跨stream依赖与分配器生命周期都要遵守其约定。显式缓冲池还要防止取消请求或异常路径过早释放资源。

依据:PyTorch · pin_memory与non_blocking使用指南 ↗ · PyTorch 2.14 · CUDA stream、等待与内存生命周期 ↗

07 · DataLoader的几个参数要一起看

参数/策略作用边界
num_workers用工作进程并行加载/预处理并非越大越好,CPU/存储/内存可能争用。
prefetch_factor多worker时每个worker提前准备的批次数目标预取量随worker数增加,不是设备预取深度。
persistent_workers跨数据迭代保留workerworker内部状态与随机性需要管理。
IterableDataset分片按worker和分布式rank划分输入流不划分可能重复读取/训练相同数据。

例如4个worker、prefetch_factor=2意味着目标预取8批。若每批CPU数据约64 MiB,仅这些批次就约512 MiB,尚未计工作进程、正在拼装的批次、锁页暂存与设备副本。真实峰值应实测。

依据:PyTorch 2.14 · DataLoader与输入数据组织 ↗

08 · 先实现稳定加载,再做跨队列重叠

CUDA/PyTorch接入片段 · 假设batch是Tensor;dataset与train_step需定义,Windows多进程使用main保护
from torch.utils.data import DataLoader

def main():
    loader = DataLoader(dataset, batch_size=..., num_workers=2,
                        pin_memory=True, prefetch_factor=2,
                        persistent_workers=True)
    for batch in loader:
        device_batch = batch.to("cuda", non_blocking=True)
        train_step(device_batch)

if __name__ == "__main__":
    main()

这个片段展示加载和主机非阻塞复制入口,不保证设备复制与计算重叠。真正的copy stream预取器还要保留源数据、在计算队列等待对应完成事件,并管理Tensor/缓冲释放。不要在每次复制后立刻全设备同步,否则会丢掉可重叠的机会。

下方CPU脚本验证输入流水的时间和每批依赖,并演示缓冲状态检查。它不启动真实DataLoader、DMA或设备stream。

昇腾CANN的aclrtMemcpyAsync文档同样区分任务下发与完成,并规定Host锁页与非锁页内存的不同返回行为。CUDA与ACL、torch_npu接口的细节要按产品和安装版本核对,不能只按函数名有Async就推断。

依据:PyTorch 2.14 · DataLoader与输入数据组织 ↗ · CANN Community 9.1.0 · aclrtMemcpyAsync与Host内存约束 ↗

09 · 先找到最慢一段,再调并发和队列

  • 计时next(loader)、CPU整理、复制、设备计算及整步,区分等待与实际工作。
  • 观察稳态消费速率、队列深度和偶发长尾;预热时队列已装满可能掩盖生产不足。
  • 记录RAM、锁页内存、设备输入缓冲及worker总资源,不只看模型显存。
  • 验证样本不丢不重、分词配置一致,分布式分片与epoch随机性正确。
  • 每秒有效token及训练质量不变时比较吞吐;推理还测请求解析/分词和首token时间。

自查:预取队列为何不能修复永久过慢的存储?为什么同stream里的non_blocking复制未必与计算重叠?复制完成后为什么还不能立刻覆盖正在计算的设备输入?

流程位置与关联方法

这个方法可能出现在多个位置;计算线包含模型计算与算子实现,执行线包含运行时、内存和通信。

资料与核查范围

原理依据以原始论文和官方文档为主;算例与时间线是教学推导。文档核查日期:2026-10-10。引用 main / latest 文档时,实际接口以安装版本为准。

代码为接入片段或教学伪代码;本页没有声称完成 NVIDIA / 昇腾硬件实测。

← 返回总表中的 M033继续阅读:参数高效微调:LoRA / QLoRA / 冻结 →
AI Infra 知识库 · 总览 → 独立讲解网站更新 2026-10-08
搜索知识库

搜索方法与基础概念 · Esc 关闭